
- Цена: $1.25
Но просто «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 — пайкой.
А вот корпус пока существует в предварительном чертеже и дубовом полене :)
(c) 2017 Источник материала