Трушин А.Н., Арутюнян М.Г. Организация соединения и обмен данными по bluetooth между Arduino и iOS-приложением

У меня есть устройство, построенное с помощью 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 (так что количество бесплатных контактов не является проблемой).

Есть ли другие варианты для этого? (протоколы, готовые библиотеки для последовательной связи и т. д.). Я пытаюсь не изобретать велосипед.

Спасибо за ваше время!

3

1 ответы

Большинство учебников 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, а также все необходимые библиотеки

Коммуникация по последовательному порту , по умному называемая как универсальный асинхронный прием / передача (UART), как правило, используется для программирования и отладки Arduino через порт USB. Существуют разные датчики и приборы, которые используют UART как метод основной связи, и иногда нам нужно объединять два и больше Arduino между собой для обмена информацией.

Тем не менее, у большинства Arduino имеется только один последовательный порт, который используется при связи по USB. Но как же связать такой контроллер с другим? Конечно использование Arduino типа Mega или подобного решает эту задачу, ведь у него до четырех последовательных портов, но если нужна связь с простыми платами из линейки Ардуино, тут нужно искать другой выход. Существует особая программная библиотека, которая имитирует UART порт на других цифровых контактах. У нее имеются несколько недостатков, но в общем она работает.

Так что нам понадобится для демонстрации подобной коммуникации:

2 Arduino контроллера

Соединительные провода

Выполните следующие шаги, для подключения двух Arduino UNO, с помощью программного последовательного порта:

1. Например, воспользуемся выводами 8 и 9 для RX и TX на обоих Arduino, соедините контакт 8 на одном Arduino с контактом 9 на другом, и контакт 9 на первом с контактом 8 на втором.

2. Соедините общий провод GND обеих Arduino вместе.

3. Подключите один Arduino к USB компьютера, и соедините вывод 5В этого контроллера с таким же выводом другого или подайте на второй отдельное питание.

Вот реализация с использованием выводов 8 и 9 для RX и TX:



Следующий код разделен на две части. Arduino мастер будет получать команды от компьютера и передавать их по программному последовательному порту. Вот первая часть кода:

// Подключаем библиотеку Software Serial

#include

// Объявляем задействованные дискретные каналы контроллера для связи

Serial.begin(9600); // Обычная скорость передачи данных

softSerial.begin(9600); // инициализация программного последовательного порта

// Проверяем получение команд от компьютера

if (Serial.available()){

// Отправляем полученную команду компьютера на программный UART

А вот и код подчиненного (слейва), который интерпретирует символы, отправленные от мастера. Если пришол символ «а», он включит встроенный светодиод. Если получен символ «х», то светодиод будет потушен:

// Подключение библиотеки Software Serial

#include

// Назначение задействованных дискретных каналов

SoftwareSerial softSerial(8, 9); // RX, TX

// Дискретный канал, на котором висит встроенный светодиод

softSerial.begin(9600); // Инициализация программного последовательного порта

pinMode(LED, OUTPUT); // Определение светодиодного вывода как выход

// Проверяем, есть ли что-нибудь в буфере программного последовательного порта

if (softSerial.available()){

// Читаем один символ из буфера программного последовательного порта и сохраняем его переменную com

int com = softSerial.read();

// Действуем соответственно полученному символу

if (com == "x"){

// Выключение светодиода

digitalWrite(LED, LOW);

else if (com == "a"){

// Включение светодиода

digitalWrite(LED, HIGH);

Как это работает

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

Разбор кода

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

SoftwareSerial softSerial(8, 9); // RX, TX

При этом будет вызвана параллельная связь, в данном случае программная. Она будет использовать вывод 8 для чтения (RX) и вывод 9 для передачи (TX). Далее подробнее остановимся на том, какие именно выводы следует выбирать.

Используя объявленный объект библиотеки, мы можем использовать все функции, характерные для обычного аппаратного параллельного порта, такие как softSerial.read(), softSerial.write() и так далее. В следующей части кода мы проверяем пришло ли что-нибудь с аппаратного порта. И если что-то пришло, мы считываем это и отправляем в программный порт:

if (Serial.available()){

softSerial.write(Serial.read());

В коде подчиненного контроллера использована самая простая реализация управления светодиодом командами через последовательный порт с одной только разницей, что тут используются команды с программного порта. Меняется только синтаксис и вместо привычных функций Serial.read(), Serial.available() и так далее нужно писать softSerial.read() и softSerial.available().

Программный UART имеет некоторые важные ограничения и недостатки. Вот некоторые из них.

Использование выводов

Мы не можем использовать любые дискретные выводы плат Arduino для организации программного порта. Для Tx, вообще-то можем использовать любые, но для Rx можно использовать только те, которые поддерживают внешние прерывания. У плат Arduino Leonardo и Micro могут быть использованы только выводы 8, 9, 10, 11, 14, 15 и 16, в то время как у Mega или Mega 2560 могут быть использованы только выводы 10, 11, 12, 13, 50, 51, 52, 53, 62, 63, 64, 65, 66, 67, 68 и 69.

Другие программные параллельные коммуникации

Можно организовывать и более одной программной последовательной связи, однако одновременно данные может получать только одно соединение. Это может становиться причиной потери данных. Но существует альтернативная библиотека программного параллельного порта, написанная Полом Стофрегеном, которая призвана решить как раз данную проблему. http://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

Обмен данными между двумя платами Arduino - очень полезная фича для многих проектов.

Например, можно чтобы одна плата Arduino управляла моторами, а вторая использовалась для подключения сенсоров и передачи управляющих сигналов на первый микроконтроллер. Реализовать обмен данными между двумя Arduino можно с использованием с помощью последовательного (serial) интерфейса. Для этого будут использоваться контакты RX и TX.

Схема подключения

На рисунке ниже показана схема подключения двух контроллеров Arduino. На схеме показаны две платы Arduino Uno, но можно использовать и Arduino Mega, если использовать соответствующие контакты RX и TX.

Обратите внимание, что необходимо объединить контакты Gnd.

В противном случае, обмен данными происходить не будет! При подключении контакт TX подключается к RX, а RX - к TX.

Особенности программы

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

Простой скетч

Можно настроить передачу данных между двумя Arduino с использованием примера Physical Pixel .

Загрузите скетч Physical Pixel, который можно найти в Arduino IDE в папке: File >> Examples >> Communication, на ваш Arduino.

На вторую плату Arduino загрузите следующий скетч:

Serial.begin(9600);

Serial.print("H");

Serial.print("L");

Когда код начинает работать, светодиод на 13 пине Arduino должен загораться и тухнуть с частотой 0.5 Гц. Для того, чтобы удостоверится в работоспособности скетча, можете изменить значение задержки (delay).

Символ "H" в приведенном выше скетче зажигает светодиод, а символ "L" отвечает за отключение светодиода. Можно расширить этот список символов для выполнения других действий.

Но этот код недостаточно гибкий и для решения более комплексных и сложных задач может не подойти.

Расширенный скетч

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

Скетч для Arduino, передающего данные

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

// скетч для Arduino, который передает данные

Serial.begin(9600);

int value=1234; // будет гораздо веселее, если это будут данные с какого-то сенсора

itoa(value, str, 10); // преобразует данные в массив символов

Serial.write(str, 4);

Скетч для Arduino, принимающего данные

Второй микроконтроллер Arduino получит массив данных в байтах и начнет их интерпретировать. Скетч для платы-ресивера приведен ниже.

// скетч для Arduino, который принимает данные

Serial.begin(9600);

Serial1.begin(9600);

if (Serial1.available()) {

while(Serial1.available() && i<4) {

str = Serial1.read();

Serial.println(str,4);

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

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

void CharToFloat(char* chars, double* value, int count) {

float multiplier;

float front =0.0, behind =0.0;

// перед точкой

while(chars[i]!="." && i<count) {

if (chars[i]==".") {

for(int j=i; j>0; j--) {

for(int k=q; k>1; k--) {

multiplier *= 10;

front+=(chars[l]-"0")*multiplier;

// после точки

while(chars[n]!="\0" && i<count) {

if (chars[n]=="\0") {

for(int j=n-1; j>i; j--) {

for(int k=q-(i+2); k>=0; k--) {

multiplier = 0.1*multiplier;

behind+=(chars[l]-"0")*multiplier;

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

© 2024 spbpda.ru
Spbpda - Обучение компьютеру