
I was getting tired of losing my place when watching YouTube tutorials, especially when I had to tab out to pause or rewind. So, I put together a USB knob that lets me easily control Pause/Play, Rewind, and Fast Forward on YouTube videos without leaving the video screen. It’s been super helpful for staying on track during tutorials.
Part List:
- Trinket microcontroller
- Rotary encoder
- Knob
Code
#include "TrinketHidCombo.h"
#define PIN_ENCODER_A 0
#define PIN_ENCODER_B 2
#define TRINKET_PINx PINB
#define PIN_ENCODER_SWITCH 1
static uint8_t enc_prev_pos = 0;
static uint8_t enc_flags = 0;
static char sw_was_pressed = 0;
void setup()
{
// set pins as input with internal pull-up resistors enabled
pinMode(PIN_ENCODER_A, INPUT);
pinMode(PIN_ENCODER_B, INPUT);
digitalWrite(PIN_ENCODER_A, HIGH);
digitalWrite(PIN_ENCODER_B, HIGH);
pinMode(PIN_ENCODER_SWITCH, INPUT);
// the switch is active-high, not active-low
// since it shares the pin with Trinket's built-in LED
// the LED acts as a pull-down resistor
digitalWrite(PIN_ENCODER_SWITCH, LOW);
TrinketHidCombo.begin(); // start the USB device engine and enumerate
// get an initial reading on the encoder pins
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; // 1 or -1 if moved, sign is direction
// note: for better performance, the code will now use
// direct port access techniques
// http://www.arduino.cc/en/Reference/PortManipulation
uint8_t enc_cur_pos = 0;
// read in the encoder state first
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 any rotation at all
if (enc_cur_pos != enc_prev_pos)
{
if (enc_prev_pos == 0x00)
{
// this is the first edge
if (enc_cur_pos == 0x01) {
enc_flags |= (1 << 0);
}
else if (enc_cur_pos == 0x02) {
enc_flags |= (1 << 1);
}
}
if (enc_cur_pos == 0x03)
{
// this is when the encoder is in the middle of a "step"
enc_flags |= (1 << 4);
}
else if (enc_cur_pos == 0x00)
{
// this is the final edge
if (enc_prev_pos == 0x02) {
enc_flags |= (1 << 2);
}
else if (enc_prev_pos == 0x01) {
enc_flags |= (1 << 3);
}
// check the first and last edge
// or maybe one edge is missing, if missing then require the middle state
// this will reject bounces and false movements
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) {
//Fast Forward youtube hotkey (l)
TrinketHidCombo.pressKey(0, 15);
TrinketHidCombo.pressKey(0, 0);
}
else if (enc_action < 0) {
//Rewind youtube hotkey (j)
TrinketHidCombo.pressKey(0, 13);
TrinketHidCombo.pressKey(0, 0);
}
// remember that the switch is active-high
if (bit_is_set(TRINKET_PINx, PIN_ENCODER_SWITCH))
{
if (sw_was_pressed == 0) // only on initial press, so the keystroke is not repeated while the button is held down
{
//PlayPause youtube hotkey (k)
TrinketHidCombo.pressKey(0, 14);
TrinketHidCombo.pressKey(0, 0);
delay(5); // debounce delay
}
sw_was_pressed = 1;
}
else
{
if (sw_was_pressed != 0) {
delay(5); // debounce delay
}
sw_was_pressed = 0;
}
TrinketHidCombo.poll(); // check if USB needs anything done
}



