USB медиаконтроллер для windows и Android устройств

Опубликовал | 06.12.2018

На разработку этой поделки меня подвигла статья USB регулятор громкости для ПК и не только.
Но просто «volume + mute» меня не устраивала, хотелось бы иметь функциональные кнопки для управления плеером. Так как ранее с AVR дела не имел от слова совсем — решил заказать одну штучку Digispark85 (с microUSB портом). Пока ехала посылка — курил мануалы, рылся в уголке некроманта и прикидывал дизайн корпуса.
В обзоре не будет распаковки, ТТХ, описания Attiny85 — сразу к делу.

Вводная часть

Контроллер использует постоянное соединение с хост устройством по USB, задействуя два порта (P3/ADC3, Р4/ADC2). Еще два порта задействуются для энкодера (о них ниже), один используется при (пере-)программировании контроллера (Р5/ADC0/Reset). Таким образом, для блока функциональных кнопок остается один порт. Проще всего его подключить по классической схеме с резистивным делителем напряжения, а для распознавания нажатий использовать встроенный в контроллер АЦП. Исходя из ограничений (см выше) остается единственный вариант такого подключения – в порт Р2/ADC1.
Энкодер будем подключать на порты Р0 и Р1 (вместо Р0 и Р2 как в базовом варианте), а встроенную в энкодер кнопку «mute» вынесем на блок кнопок (сорри за тавтологию).

Принципиальная схема контроллера

Обратите внимание: из схемы исключен светодиод подключенный к порту PB1.

Немного математики

Количество дополнительных кнопок зависит от вашей фантазии, но ограничено объемом программного кода, точностью АЦП и выбранной разрядностью. Для моих нужд достаточно 5 кнопок и 8-битного режима АЦП. В качестве опорного напряжения лучше всего использовать Vcc (при питании от USB это примерно +5в +-допуски), при этом результаты отсчетов АЦП не будут зависеть от колебаний этого напряжения. Для четкого распознавания нажатий я подобрал номиналы резисторов таким образом, чтобы разница между отсчетами АЦП для соседних кнопок была не менее 40.
Для упрощения расчетов сделал таблицу (прилагаю) по которой можно подобрать оптимальные параметры делителя и рассчитать пороги для определения нажатой кнопки. Три правые колонки с серым заголовком содержат реальные измерения и расчеты для АЦП после сборки блока, в программном коде используются именно они.

Скорость преобразования АЦП зависит текущей тактовой частоты микроконтроллера и выбранного делителя, т.н. prescaler’а. Устройство работает на 16.5МГц, поэтому для функционирования АЦП необходимо будет установить максимальное значение prescaler =128. Это обеспечит тактирование с частотой примерно 128КГц, время на одно преобразование составит 1/10000 сек.

Программная часть

Представляет модифицированный код из TrinketVolumeKnobPlus, из которого убрано лишнее и добавлено нужное.
При нажатии и удержании любой кнопки подается только одна команда (блокировка от keystroke repeat), но при желании можно модифицировать код таким образом, чтобы при нажатии << / >> контроллер повторял соответствующие команды.
После компиляции прошивка занимает примерно 60% от отведенных 8КБ flash памяти (часть занимает специальный загрузчик).

Листинг под спойлером
  #include "TrinketHidCombo.h"    #define PIN_ENCODER_A 0  #define PIN_ENCODER_B 1 //changeв from P2 port  #define TRINKET_PINx PINB    static uint8_t enc_prev_pos = 0;  static uint8_t enc_flags = 0;  static char sw_was_pressed = 0; //keystroke flag  static uint8_t adc_val = 255; //ADC value      void setup()  {    pinMode(PIN_ENCODER_A, INPUT);    pinMode(PIN_ENCODER_B, INPUT);    digitalWrite(PIN_ENCODER_A, HIGH);    digitalWrite(PIN_ENCODER_B, HIGH);      // init ADC1 at 8bit 16MHz 128 prescaler    ADMUX =        (1 << ADLAR) |     // left shift result        (0 << REFS1) |     // Sets ref. voltage to VCC, bit 1        (0 << REFS0) |     // Sets ref. voltage to VCC, bit 0        (0 << MUX3)  |     // use ADC1 for input (PB2), MUX bit 3        (0 << MUX2)  |     // use ADC1 for input (PB2), MUX bit 2        (0 << MUX1)  |     // use ADC1 for input (PB2), MUX bit 1        (1 << MUX0);       // use ADC1 for input (PB2), MUX bit 0    ADCSRA =         (1 << ADEN)  |     // Enable ADC         (1 << ADPS2) |     // set prescaler to 128, bit 2        (1 << ADPS1) |     // set prescaler to 128, bit 1        (1 << ADPS0);     // set prescaler to 128, bit 0              TrinketHidCombo.begin();    if (digitalRead(PIN_ENCODER_A) == LOW) {      enc_prev_pos |= (1 << 0);    }    if (digitalRead(PIN_ENCODER_B) == LOW) {      enc_prev_pos |= (1 << 1);    }  }    void loop()  {    int8_t enc_action = 0;    uint8_t enc_cur_pos = 0;    if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_A)) {      enc_cur_pos |= (1 << 0);    }    if (bit_is_clear(TRINKET_PINx, PIN_ENCODER_B)) {      enc_cur_pos |= (1 << 1);    }      if (enc_cur_pos != enc_prev_pos) {      if (enc_prev_pos == 0x00) {        if (enc_cur_pos == 0x01) {          enc_flags |= (1 << 0);        }        else if (enc_cur_pos == 0x02) {          enc_flags |= (1 << 1);        }      }        if (enc_cur_pos == 0x03) {        enc_flags |= (1 << 4);      }      else if (enc_cur_pos == 0x00) {        if (enc_prev_pos == 0x02) {          enc_flags |= (1 << 2);        }        else if (enc_prev_pos == 0x01) {          enc_flags |= (1 << 3);        }          if (bit_is_set(enc_flags, 0) && (bit_is_set(enc_flags, 2) || bit_is_set(enc_flags, 4))) {          enc_action = 1;        }        else if (bit_is_set(enc_flags, 2) && (bit_is_set(enc_flags, 0) || bit_is_set(enc_flags, 4))) {          enc_action = 1;        }        else if (bit_is_set(enc_flags, 1) && (bit_is_set(enc_flags, 3) || bit_is_set(enc_flags, 4))) {          enc_action = -1;        }        else if (bit_is_set(enc_flags, 3) && (bit_is_set(enc_flags, 1) || bit_is_set(enc_flags, 4))) {          enc_action = -1;        }          enc_flags = 0; // reset for next time      }    }      enc_prev_pos = enc_cur_pos;      if (enc_action > 0) {      TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_UP);    }    else if (enc_action < 0) {      TrinketHidCombo.pressMultimediaKey(MMKEY_VOL_DOWN);    }    // Buttons decoder block  ADCSRA |= (1 << ADSC);         // start ADC measurement  while (ADCSRA & (1 << ADSC)); // waiting for completion  adc_val = ADCH; // read ADC register  if (adc_val < 210) { //some button pressed    if (sw_was_pressed == 0) { // check for hold down button      if (adc_val < 150) {        if (adc_val < 95) {          if (adc_val < 55) {            if (adc_val < 35) { //sw1 is pressed              TrinketHidCombo.pressMultimediaKey(MMKEY_MUTE);              }            else            { //sw2 is pressed            TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_PREV_TRACK);            }          }          else          { //sw3 is pressed          TrinketHidCombo.pressMultimediaKey(MMKEY_STOP);          }        }        else        { //sw4 is pressed        TrinketHidCombo.pressMultimediaKey(MMKEY_PLAYPAUSE);        }      }      else      { //sw5 is pressed      TrinketHidCombo.pressMultimediaKey(MMKEY_SCAN_NEXT_TRACK);      }    delay(5);    sw_was_pressed = 1; // set keystroke flag    }   }   else   {   sw_was_pressed = 0; // clear keystroke flag   }  TrinketHidCombo.poll();  }  

Печатная плата и корпус

Печатная плата — это сам Digispark. Подключение к внешним устройствам — через штатный microUSB порт.
Для блока кнопок я использовал платку управления от старого сгоревшего монитора LG, поменяв в соответствии с таблицей SMD резисторы. Саму платку и поддерживающую рамку с толкателями немного укоротил, подклеил для жесткости бортики убрал гнездо для шлейфа. Соединение с Digispark85 — пайкой.

А вот корпус пока существует в предварительном чертеже и дубовом полене :)


Планирую купить 0 Добавить в избранное 0

(c) 2017 Источник материала

Рекламные ссылки