Включение и выключение светодиода с помощью Arduino. Часть 2 — двусторонний контроль
В первой части я показал, как управлять одним светодиодом из приложения, созданного в App Inventor. Это работало нормально, но было очень ограниченно. Вы могли управлять только одним светодиодом, и управление было односторонним — от приложения к Arduino. А что если вы хотите двустороннее управление светодиодом и возможность контролировать его также со стороны Arduino? А что если вы хотите управлять более чем одним светодиодом?
В этом руководстве мы рассмотрим добавление двусторонней связи. Здесь мы управляем светодиодом, но вы можете использовать это для любых целей.
В первом примере вы могли управлять светодиодом только из Android-приложения, здесь мы расширим пример, чтобы можно было также управлять светодиодом со стороны Arduino. Когда светодиод включается или выключается Arduino, мы хотим, чтобы кнопка в приложении обновлялась и показывала текущее состояние светодиода.
В первом примере использовались методы, подходящие только для управления одним светодиодом. На этот раз мы постараемся сделать так, чтобы скетч Arduino и приложение AI2 легко масштабировались, и после создания базового приложения добавление дополнительных кнопок и элементов управления было достаточно простым.
Настройка экрана
Экран довольно простой. У нас есть кнопка для активации Bluetooth-подключения к Arduino и кнопка для управления светодиодом. Другие элементы включают Bluetooth Client, Notifier и таймер (Clock).
Таймер (Clock1) используется для проверки входящих данных и настроен на срабатывание каждые 100 мс. Это 10 раз в секунду — более чем достаточно для данного примера. TimerEnabled установлен в false (не отмечен), что означает, что таймер выключен при запуске приложения. Мы включаем таймер в блоках, когда пользователь устанавливает Bluetooth-соединение.
Команды
Чтобы максимально упростить код на Arduino, мы будем использовать команды, которые легко обрабатывать. Команды, отправляемые из приложения, используют базовый код. <[тип элемента][номер][флаг вкл/выкл]>
<L10> — выключить LED 1<L11> — включить LED 1<L20> — выключить LED 2<L21> — включить LED 2<L30> — выключить LED 3<L31> — включить LED 3Вы заметите, что все команды имеют одинаковую длину. Это упрощает написание кода на Arduino. Использование типа команды (L для LED) позволяет нам позже добавлять дополнительные команды, например, S для ползунка (slider), без необходимости вносить большие изменения в приложение.
Поскольку мы хотим сделать приложение легко расширяемым, а AI2 имеет продвинутые функции обработки текста, мы можем использовать немного более сложные команды при отправке данных от Arduino к приложению. Эти команды будут в формате <[тип элемента],[номер],[флаг вкл/выкл]>. Обратите внимание на дополнительные запятые…
Команды, отправляемые от Arduino к приложению при изменении состояния LED:
<L,1,0> — LED 1 выключен<L,1,1> — LED 1 включён<L,2,0> — LED 2 выключен<L,2,1> — LED 2 включён<L,3,0> — LED 3 выключен<L,3,1> — LED 3 включёнКонечно, мы могли бы использовать те же команды, что и при отправке данных из приложения на Arduino, но дополнительные запятые позволяют нам легко разбить команду на отдельные части.
Приём данных в приложении
Как уже упоминалось, на этот раз мы будем использовать двустороннюю связь. Это означает, что нам нужна возможность принимать команды в приложении. Эта часть, вероятно, самая сложная из новых дополнений к приложению и состоит из:
Bluetooth-связь (как и любая последовательная связь) не на 100% надёжна, и есть вещи, за которыми нужно следить:
Android-приложение в деталях
Файл App Inventor 2 aia можно скачать в конце поста.
Блоки
Когда приложение запускается, отображается экран, и оно ожидает пользовательского ввода. Прежде чем управлять светодиодами, пользователь должен подключиться к Arduino. Для этого нужно нажать кнопку Bluetooth и выбрать Bluetooth-модуль, подключённый к Arduino. Если пользователь нажмёт одну из кнопок LED до установки соединения, будет показано сообщение об ошибке.
При нажатии кнопки Bluetooth выполняется блок BT_BUTTON.Click.
Эта функция:
Я использую список для сопряжённых устройств, потому что в момент, когда я впервые писал это, не мог разобраться, как узнать количество сопряжённых устройств только из ListPicker.
When BT_LP.AfterPicking
После того как пользователь выбирает одно из сопряжённых устройств из списка, вызывается блок BT_LP.AfterPicking. Именно здесь приложение пытается подключиться к Arduino. Если подключение успешно, текст кнопки Bluetooth меняется на «Connected» и запускается таймер. Если попытка подключения не удалась, отображается сообщение об ошибке. Таймер используется для проверки входящих данных.
When BT_LP.AfterPicking
Если пользователь нажимает кнопку Bluetooth при наличии активного подключения, ему предлагается закрыть соединение.
После того как пользователь выбирает один из вариантов уведомления, вызывается блок Notifier1.AfterChoosing. Если пользователь выбрал «Yes», соединение закрывается.
После подключения Bluetooth и запуска таймера приложение продолжает проверять входящие данные, а также отслеживать нажатие кнопки LED.
Кнопка LED
При нажатии кнопки LED вызывается функция LED1_BUTTON_btn.Click.
Здесь приложение проверяет текущий текст кнопки, и если это «OFF»:
<L11> на Arduino.Если текст кнопки не «OFF» (то есть «ON»):
<L10> на Arduino.Если Bluetooth не подключён, отображается сообщение об ошибке.
Таймер и входящие данные
Скетч Arduino проверяет входящие данные при каждой итерации основного цикла. В AI2 у нас нет основного цикла, и нам нужно использовать таймер. Каждый раз, когда таймер срабатывает, вызывается специальная функция. Когда таймер срабатывает, приложение запоминает, где оно находится и что делает, затем переходит к функции таймера, выполняет весь код в ней и возвращается к предыдущему месту и продолжает работу.
Первое, что делает функция — останавливает таймер. Это предотвращает вызов функции таймера до её завершения. Затем вызывается функция BT_getNewData.
Здесь приложение проверяет наличие новых данных, и если они есть, добавляет их в глобальную переменную BT_receivedData_Buffer.
Нет гарантий, что мы получим полную команду за один раз, поэтому нам нужно собирать входящие данные и хранить их в буфере. Затем мы проверяем буфер на наличие команды.
Далее идёт функция BT_trimToStartMarker. Она обрезает буфер так, чтобы первый символ был маркером начала данных (символ <). Для данного примера это не обязательно, но это помогает, когда команд больше или данные отправляются быстрее.
После этого мы проверяем наличие команд, вызывая функцию BT_processBuffer.
Здесь мы проверяем, что у нас есть и начальный маркер, и конечный маркер, и что они расположены в правильном порядке (это не совсем обязательно, так как мы уже обрезали данные с помощью функции BT_trimToStartMarker).
Затем фактическая команда извлекается и отправляется в функцию processCommand.
Здесь команда разделяется на отдельные элементы с помощью блока split. Мы используем запятые в качестве разделителей. В данном примере такой способ разделения не обязателен, поскольку у нас команды фиксированной длины, но если мы позже добавим новые команды другой длины, это будет очень полезно.
Блок split помещает части в список (AI2-версия массива). Три части доступны через блок select list item.
Функция processCommand проверяет, является ли первое свойство «L» для LED, и если да, вызывает функцию processLEDCommand.
Опять же, для данного простого примера это не обязательно, но, делая так, мы можем легко добавить дополнительные команды, добавив дополнительное условие в функции processCommand и затем добавив новую функцию для обработки новой команды.
Функция processLEDCommand сначала проверяет, является ли команда для LED1:
а затем проверяет, был ли светодиод выключен:
Если светодиод был выключен, кнопка LED меняется соответствующим образом.
Затем функция проверяет, равно ли значение по индексу 3 единице, и если да, кнопка LED устанавливается для отображения, что светодиод включён.
Если данные по индексу 3 не равны 0 и не равны 1, то у нас ошибка. В данном примере никаких действий не предпринимается, но вы можете добавить предупреждающее сообщение или каким-то образом отобразить неправильную команду.
Схема и скетч Arduino
Схема
Схема состоит из светодиода на пине D2 и кнопки на пине D5.
Исправлена диаграмма. У меня были перепутаны пины Arduino RX и TX.
Примечание: На схеме показан резистор 330 Ом для светодиода, тогда как на макетной плате — резистор 220 Ом. 220 Ом — это то, что было под рукой…
Скетч Arduino
Функция Setup() инициализирует пины для светодиода и кнопки, открывает последовательный канал для отладочных сообщений и открывает AltSoftSerial для Bluetooth-модуля.
void setup()
{
// Устанавливаем пин кнопки на вход
pinMode(SWITCH1_PIN, INPUT);
// Устанавливаем пин LED на выход и LOW
pinMode(LED1_PIN, OUTPUT);
digitalWrite(LED1_PIN,LOW);
// Открываем последовательное соединение для отладки
Serial.begin(9600);
Serial.print("Sketch: "); Serial.println(__FILE__);
Serial.print("Uploaded: "); Serial.println(__DATE__);
Serial.println(" ");
// Открываем программный последовательный порт для Bluetooth-модуля
BTserial.begin(9600);
Serial.println("AltSoftSerial started at 9600");
newData = false;
} // void setup()
Основной цикл очень простой:
void loop()
{
checkSwitch();
recvWithStartEndMarkers(); // проверяем новые команды
if (newData) { processCommand(); } // обрабатываем новую команду
}
Функция checkSwitch проверяет, была ли нажата кнопка, и если да, переключает состояние светодиода и отправляет соответствующую команду в Android-приложение.
void checkSwitch()
{
// Простая функция переключения с простым дребезгоподавлением
boolean state1 = digitalRead(SWITCH1_PIN); delay(1);
boolean state2 = digitalRead(SWITCH1_PIN); delay(1);
boolean state3 = digitalRead(SWITCH1_PIN); delay(1);
if ((state1 == state2) && (state1==state3))
{
switch1_State = state1;
if ( (switch1_State == HIGH) && (oldswitch1_State == LOW) )
{
LED1_State = ! LED1_State;
if ( LED1_State == HIGH)
{
BTserial.print("<L,1,1>" );
digitalWrite(LED1_PIN,HIGH);
Serial.println("Sent - <L,1,1>");
}
else
{
BTserial.print("<L,1,0>");
digitalWrite(LED1_PIN,LOW);
Serial.println("Sent - <L,1,0>");
}
}
oldswitch1_State = switch1_State;
}
}
Функция processCommand проверяет полученные данные на наличие команды и, если это команда, изменяет состояние светодиода и LED1_State соответственно.
void processCommand()
{
Serial.print("receivedChars = ");
Serial.println(receivedChars);
if (strcmp ("L10",receivedChars) == 0)
{
digitalWrite(LED1_PIN,LOW);
LED1_State = LOW;
Serial.println("LED1 LOW");
}
else if (strcmp ("L11",receivedChars) == 0)
{
digitalWrite(LED1_PIN,HIGH);
LED1_State = HIGH;
Serial.println("LED1 HIGH");
}
receivedChars[0] = '\0';
newData = false;
}