04.04.2012

Freeduino wireless (1)

Однажды мне повезло выкроить время на посещение мероприятия Hard StartUp в рамках нашего провинциального hackspace. Сам я от выступления категорически отказался под предлогом бессистемного расстройства вокабулярия к концу рабочего дня, но послушал выступающих с интересом; явно бросалось в глаза, что докладчики испытывали недостаток в пульте дистанционного управления презентацией: приходилось просить листать слайды сидящего за ноутбуком человека.

Ясно, что радиокружок хакспейс выруливает в основном за счет энтузиазма его активистов и базы ИТМО, и у ребят просто могло отсутствовать нужное оборудование. Уверен, что это было не последнее их мероприятие с проведением презентаций, поэтому предлагаю свой Arduino-вариант аналогичного устройства - надеюсь, оно пригодится не только им.

Ингредиенты

Сначала создадим прототип - из того, что найдется под рукой. Мне всегда несколько проще, потому что у меня под рукой целый mk90.ru/store ;)

1. Плата Freeduino32u4 - содержит единственный чип ATmega32u4, который может изображать из себя клавиатуру, и поэтому именно ее мы будем подключать к компьютеру с презентацией;
2. Плата Freeduino Nano - будет находиться в руке у докладчика, считывая нажатия на кнопки прокрутки и передавая их Freeduino 32u4;
3. Кнопки, проводки и пара беспаечных макеток, чтобы не хвататься за паяльник по пустякам.
4. Комплект модулей беспроводной связи - наверное, самые дешевые устройства для передачи низкоскоростного информационного потока через эфир для небольших расстояний.


Немного теории

Прежде всего, вот заранее мой ответ фанатикам  IEEE 802.11, на вопрос "почему в списке нет wifi?!": не палите из пушки по микробам! В нашей задаче не нужна маршрутизация, одновременная коммуникация между несколькими устройствами и повышенная проникающая способность радиоизлучения. Нас устроят гораздо более простые устройства  - т.н. RF-трансиверы. В основном, они отличаются мощностью, несущей частотой и способом модуляции. Возьмем недорогой маломощный модуль, используемый в самоходных радиоуправляемых игрушках и работающий на нелицензируемой в России частоте 433 МГц, с модуляцией ASK.

"ASK" означает, что частота и фаза сигнала остаются постоянными, а для кодирования единицы и нуля варьируется амплитуда. Подобные приемники и передатчики можно собрать и самостоятельно, подробнее об этом можно прочесть, например у Алексея Кравченко в книге "10 практических устройств на  AVR, книга 2".

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

Что-то решается программно, что-то - схемотехнически, но самое главное - такие приемопередатчики поддерживаются ArduinoIDE с помощью специальной библиотеки VirtualWire.

Схема

Рисуем схему, которая будет подключаться к USB:


И ту, которая в руке у лектора (с кнопочками):


Предполагаем пока, что обе платы питаются от компьютера, через USB.

Макет

Как бы не ругали беспаечную макетку за надежность контактов, на несложных схемах работать с ней весьма удобно. Я использую сочетание жестких и гибких проводов (вообще, это дело вкуса - гибкими быстрее, но менее наглядно). С платой 32u4:


С платой Freeduino Nano:


Кнопки устанавливать я поленился, выбрав в качестве имитации их нажатия втыкание проводка в дырочку рядом с соответствующим контактом Nano (естественно, другим концом провод воткнут в GND).

Скетчи

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

Скетч передатчика:

#include <VirtualWire.h>

#define buttonsCount  3
int bState[buttonsCount];
int bNum[buttonsCount] = {2,3,4};

void setup() {
  // buttons
  for (byte i=0;i<buttonsCount;i++) {
    bState[i] = HIGH;
    pinMode(bNum[i],INPUT); // для ясности, после сброса все пины и так уже входы
    digitalWrite(bNum[i],HIGH); // pullup
  }
  // инициализируем последовательный порт - только для отладки
  Serial.begin(9600);
  // инициализируем библиотеку VirtualWire
  vw_setup(2000);      // скорость обмена в бит/сек
}

void loop() {
   for  (byte i=0;i<buttonsCount;i++) {
     if (digitalRead(bNum[i])!=bState[i]) {
       if (bState[i] == HIGH) {
         char key[2] = {i+1,'\0'}; 
         vw_send((uint8_t *)key, strlen(key));
         vw_wait_tx();
         bState[i] = LOW;
       } else bState[i] = HIGH;
     }
   }
   delay(100);
}

Перед началом использования кнопок, в setup включаем подтягивающие резисторы (зачем нужны подтягивающие резисторы, я уже писал). Далее, сканируем изменение состояния кнопки и, если она перешла из состояния "свободна" в "нажато", генерируем и передаем через беспроводной модуль сообщение с номером кнопки (единственный байт со значением 1, 2 или 3). Задержка в конце - примитивная борьба с дребезгом.

Скетч приемника:

#include <VirtualWire.h>

#define KEY_LEFT 80
#define KEY_RIGHT 79
#define KEY_ESC 41
#define KEY_F5 62


void setup() {
  pinMode(13, OUTPUT); // для индикации приема
  vw_setup(2000); // скорость обмена в бит/сек 
  vw_rx_start();  // инициализация и запуск приемника
}

void loop() {  
  uint8_t buf[VW_MAX_MESSAGE_LEN];
  uint8_t buflen = VW_MAX_MESSAGE_LEN;

  if (vw_get_message(buf, &buflen)) // неблокирующее чтение
  {
     digitalWrite(13, HIGH); // Зажигаем светодиод L - принято сообщение  
     byte key = buf[0];
     
     if (key==1) Keyboard.print(' ');
     else {
      { 
        KeyReport kbd = {0};
        if (key==2) kbd.keys[0] = KEY_LEFT;
         else kbd.keys[0] = KEY_RIGHT;
        Keyboard.sendReport(&kbd);
      }
      {
        KeyReport kbd = {0};
        Keyboard.sendReport(&kbd);
      }                        
     }     
     digitalWrite(13, LOW);
   }
   delay(200); 
}


Этот скетч будет корректно работать (и вообще скомпилируется) только с последним пропатченным ядром ArduinoIDE для Freeduino 32u4 - там функция sendReport сделана публичной, чтобы скетч был способен генерировать нажатия не только на символьные клавиши (буква 'A', цифра '8' или пробел), но и управляющие коды - (стрелки, F-клавиши и т.п.). После вызова sendReport с кодом нажатой клавиши, нужно всегда делать еще один, чтобы сообщить о ее отпускании, в противном случае через некоторое время получим режим повтора.

В данном скетче кнопка №1 транслируется в пробел, кнопка №2 - стрелка влево, кнопка №3 - стрелка вправо. Функция vw_get_message вернет нам полученное сообщение только в том случае, если совпала подсчитанная контрольная сумма - но для нас это полностью прозрачно, просто учитывайте, что она избавляет нас от необходимости проверять достоверность принятой информации с точки зрения эфирных искажений нашего примитивного ASK-сигнала ;)

Roadmap

Итак, макет завелся. Что дальше?

Устройства для реальной жизни можно делать и на основе готовых плат Freeduino, и с помощью ЛУТ-а. Но имейте ввиду, что ATmega32u4 выпускается только в SMD (в отличие от ATmega328P).

По питанию приемника все понятно - экономить не надо. А вот с передатчиком придется повозиться - продумать батарейную схему питания, позаботиться об экономном расходовании ее ресурса.

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

Все это вы уже можете начинать самостоятельно или же подождать моего продолжения, если когда оно появится ;)