Включение и выключение светодиода с помощью Arduino. Часть 2 — двусторонний контроль

В первой части я показал, как управлять одним светодиодом из приложения, созданного в App Inventor. Это работало нормально, но было очень ограниченно. Вы могли управлять только одним светодиодом, и управление было односторонним — от приложения к Arduino. А что если вы хотите двустороннее управление светодиодом и возможность контролировать его также со стороны Arduino? А что если вы хотите управлять более чем одним светодиодом?

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

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-1.jpg

В первом примере вы могли управлять светодиодом только из Android-приложения, здесь мы расширим пример, чтобы можно было также управлять светодиодом со стороны Arduino. Когда светодиод включается или выключается Arduino, мы хотим, чтобы кнопка в приложении обновлялась и показывала текущее состояние светодиода.

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

Настройка экрана

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-2.jpg

Экран довольно простой. У нас есть кнопка для активации Bluetooth-подключения к Arduino и кнопка для управления светодиодом. Другие элементы включают Bluetooth Client, Notifier и таймер (Clock).

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-3.jpg

Таймер (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 можно скачать в конце поста.

Блоки

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-4.jpg

Когда приложение запускается, отображается экран, и оно ожидает пользовательского ввода. Прежде чем управлять светодиодами, пользователь должен подключиться к Arduino. Для этого нужно нажать кнопку Bluetooth и выбрать Bluetooth-модуль, подключённый к Arduino. Если пользователь нажмёт одну из кнопок LED до установки соединения, будет показано сообщение об ошибке.

При нажатии кнопки Bluetooth выполняется блок BT_BUTTON.Click.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-22.jpg

Эта функция:

— проверяет, включён ли Bluetooth, и если да,
— проверяет, нет ли активного подключения, если нет,
— сопряжённые устройства копируются в список,
— проверяется размер списка, чтобы убедиться, что есть хотя бы одно сопряжённое устройство, и если есть,
— содержимое списка сопряжённых устройств копируется в ListPicker,
— ListPicker активируется.
Если Bluetooth не включён, отображается сообщение об ошибке.
Если уже есть активное подключение, пользователю предлагается закрыть его.
Если нет сопряжённых устройств, отображается сообщение об ошибке.

Я использую список для сопряжённых устройств, потому что в момент, когда я впервые писал это, не мог разобраться, как узнать количество сопряжённых устройств только из ListPicker.

When BT_LP.AfterPicking

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-5.jpg

После того как пользователь выбирает одно из сопряжённых устройств из списка, вызывается блок BT_LP.AfterPicking. Именно здесь приложение пытается подключиться к Arduino. Если подключение успешно, текст кнопки Bluetooth меняется на «Connected» и запускается таймер. Если попытка подключения не удалась, отображается сообщение об ошибке. Таймер используется для проверки входящих данных.

When BT_LP.AfterPicking

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-7.jpg

Если пользователь нажимает кнопку Bluetooth при наличии активного подключения, ему предлагается закрыть соединение.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-6.jpg

После того как пользователь выбирает один из вариантов уведомления, вызывается блок Notifier1.AfterChoosing. Если пользователь выбрал «Yes», соединение закрывается.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-8.jpg

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

Кнопка LED

При нажатии кнопки LED вызывается функция LED1_BUTTON_btn.Click.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-9.jpg

Здесь приложение проверяет текущий текст кнопки, и если это «OFF»:

— текст меняется на «ON»,
— цвет кнопки меняется на зелёный, и
— отправляется команда <L11> на Arduino.

Если текст кнопки не «OFF» (то есть «ON»):

— текст меняется на «OFF»,
— цвет кнопки меняется на красный, и
— отправляется команда <L10> на Arduino.

Если Bluetooth не подключён, отображается сообщение об ошибке.

Таймер и входящие данные

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

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-10.jpg

Первое, что делает функция — останавливает таймер. Это предотвращает вызов функции таймера до её завершения. Затем вызывается функция BT_getNewData.

Здесь приложение проверяет наличие новых данных, и если они есть, добавляет их в глобальную переменную BT_receivedData_Buffer.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-11.jpg

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

Далее идёт функция BT_trimToStartMarker. Она обрезает буфер так, чтобы первый символ был маркером начала данных (символ <). Для данного примера это не обязательно, но это помогает, когда команд больше или данные отправляются быстрее.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-12.jpg

После этого мы проверяем наличие команд, вызывая функцию BT_processBuffer.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-13.jpg

Здесь мы проверяем, что у нас есть и начальный маркер, и конечный маркер, и что они расположены в правильном порядке (это не совсем обязательно, так как мы уже обрезали данные с помощью функции BT_trimToStartMarker).

Затем фактическая команда извлекается и отправляется в функцию processCommand.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-14.jpg

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

Блок split помещает части в список (AI2-версия массива). Три части доступны через блок select list item.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-15.jpg

Функция processCommand проверяет, является ли первое свойство «L» для LED, и если да, вызывает функцию processLEDCommand.

Опять же, для данного простого примера это не обязательно, но, делая так, мы можем легко добавить дополнительные команды, добавив дополнительное условие в функции processCommand и затем добавив новую функцию для обработки новой команды.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-16.jpg

Функция processLEDCommand сначала проверяет, является ли команда для LED1:

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-17.jpg

а затем проверяет, был ли светодиод выключен:

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-18.jpg

Если светодиод был выключен, кнопка LED меняется соответствующим образом.

Затем функция проверяет, равно ли значение по индексу 3 единице, и если да, кнопка LED устанавливается для отображения, что светодиод включён.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-19.jpg

Если данные по индексу 3 не равны 0 и не равны 1, то у нас ошибка. В данном примере никаких действий не предпринимается, но вы можете добавить предупреждающее сообщение или каким-то образом отобразить неправильную команду.

Схема и скетч Arduino

Схема

Схема состоит из светодиода на пине D2 и кнопки на пине D5.

https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-21.jpg https://alashed-media.s3.eu-north-1.amazonaws.com/wiki/martyncurrey/turning-a-led-on-and-off-part2-20.jpg

Исправлена диаграмма. У меня были перепутаны пины 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()

Основной цикл очень простой:

— Вызывает функцию checkSwitch, которая проверяет, была ли нажата кнопка.
— Вызывает recvWithStartEndMarkers() для проверки новых данных от Bluetooth.
— Если новые данные получены, вызывает processCommand() для обработки команды.
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;

}

Скачать

Скачать файл App Inventor 2 aia и скетч Arduino.