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

  • Цена: $1.25
  • На разработку этой поделки меня подвигла статья USB регулятор громкости для ПК и не только.

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

    В обзоре не будет распаковки, ТТХ, описания Attiny85 — сразу к делу.

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

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

    Энкодер будем подключать на порты Р0 и Р1 (вместо Р0 и Р2 как в базовом варианте), а встроенную в энкодер кнопку «mute» вынесем на блок кнопок (сорри за тавтологию).

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

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

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

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

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

    Для упрощения расчетов сделал таблицу (прилагаю) по которой можно подобрать оптимальные параметры делителя и рассчитать пороги для определения нажатой кнопки. Три правые колонки с серым заголовком содержат реальные измерения и расчеты для АЦП после сборки блока, в программном коде используются именно они.

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

    Скорость преобразования АЦП зависит текущей тактовой частоты микроконтроллера и выбранного делителя, т.н. 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 — пайкой.

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

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

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

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

Оцените статью