2024-04-27 DIY鍵盤快捷鍵

因為最近開始玩Helldivers 2遊戲, 裡面呼叫武器與支援有很多需要數個上下左右組合按鈕去呼叫. 所以便萌生了自己製作的想法.

路過彰化銀行剛好看到招牌被拆下來

這個還蠻好喝的

這邊好特別!

邊吃飯邊看美食動畫XD


電路圖先畫好

IMG_6975s
IMG_6978s

MCU用ATtiny85. 一片大概兩百不到, 超便宜!

IMG_6984s

先大概放一下感覺一下

IMG_6986s

然後就照著電路圖去焊電路啦~

用0603的電阻比較省空間.

所有照片

剩下的就是用Arduino IDE去把程式寫好

ATtiny85 board

https://projecthub.arduino.cc/alaspuresujay/use-an-attiny85-with-arduino-ide-d847c5

加板子, Arduino IDE設定裡加入這個網址

https://raw.githubusercontent.com/digistump/arduino-boards-index/master/package_digistump_index.json

去Borad manager找到Digistump AVR Boards 選安裝

下載驅動程式

https://github.com/LilyGO/DigiSpark-ATtiny85-driver-install

ATtiny85的板子要按下Arduino裡IDE的Upload再插板子才會燒code

完整程式碼

#include "DigiKeyboard.h" //For Digispark board

#define DEBOUNCE_DELAY 30
#define DELAY1  350
#define DELAY2  200
#define DELAY3  1000
#define MAX_RANDOM_DELAY 50
#define MAX_COMMAND_KEY_NUM 10

enum BT_NUM {
  BT_NC = 0,
  BT_1 = 1,
  BT_2,
  BT_3,
  BT_4,
  BT_5,
  BT_6,
  BT_7,
  BT_8,
  BT_9,
  BT_10,
  BT_NA,
};

//Hardware setting
//define pin
//const int pin0 = 0;
const int LED_R = 1; //Blink the LED during send key
//const int pin2 = 2;
//int pin3 = 3; //USB D+
//int pin4 = 4; //USB D-
//int pin5 = 5; //Unstable don't use

//other HW settings
const int BT_VOLT_PIN = A1; //Analog Button in one input  //P2
const float GPIO_VOL =  4.8;  //GPIO volt (3.3V or 5V)
const int TOTAL_BT_NUM = 10; //Total button number
const int R1 = 1000;  //R1 ohm value
const int R2 = 470;   //R2 ohm value
float BT_volt[TOTAL_BT_NUM + 1] = {0};
const float BT_volt_offset = 0.05;

//deboundce
BT_NUM last_read_BT = BT_NA;
BT_NUM last_BT = BT_NA;
unsigned long last_read_BT_time = 0;
const int deBounce_time = DEBOUNCE_DELAY;  //ms

//按鈕串電路
//https://104.es/index.php/2018/05/28/arduino-unit-15/
//指令
//https://www.thegamer.com/helldivers-2-complete-stratagem-button-input-list/#how-to-change-keybinds-for-stratagem-inputs-on-pc
//https://forum.gamer.com.tw/C.php?bsn=24827&snA=1344

//任務
const byte keycmd_reinforce[MAX_COMMAND_KEY_NUM] = {KEY_W, KEY_S, KEY_D, KEY_A, KEY_W, 0}; //增援
const byte keycmd_resupply[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_S, KEY_W, KEY_D, 0}; //重新補給

//戰略配備
//愛國行政中心
const byte keycmd_machine_gun[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_S, KEY_W, KEY_D, 0}; //機槍
const byte keycmd_anti_Materiel_rifle[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_D, KEY_W, KEY_S, 0}; //反器材步槍
const byte keycmd_stalwart[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_S, KEY_W, KEY_W, KEY_A, 0}; //盟友
const byte keycmd_anti_tank[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_S, KEY_A, KEY_W, KEY_D, 0}; //反坦克消耗品
const byte keycmd_recoilless_rifle[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_D, KEY_D, KEY_A, 0}; //無後座力步槍
const byte keycmd_flamethrower[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_W, KEY_S, KEY_W, 0}; //火焰噴射器
const byte keycmd_autocannon[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_S, KEY_W, KEY_W, KEY_D, 0}; //機炮
//重機槍
const byte keycmd_railgun[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_D, KEY_A, KEY_S, KEY_W, KEY_A, KEY_D, 0}; //磁軌炮
const byte keycmd_spear[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_S, KEY_W, KEY_S, KEY_S, 0}; //長矛

//軌道大砲
const byte keycmd_380mm_barrage[MAX_COMMAND_KEY_NUM] = {KEY_D, KEY_S, KEY_W, KEY_W, KEY_A, KEY_S, KEY_S, 0}; //軌道380mm高爆彈彈幕 → ↓ ↑ ↑ ← ↓ ↓

//機庫
const byte keycmd_eagle_500kg_bomb[MAX_COMMAND_KEY_NUM] = {KEY_W, KEY_D, KEY_S, KEY_S, KEY_S, 0}; //飛鷹500kg炸彈 ↑ → ↓ ↓ ↓

//艦橋
const byte keycmd_precision_strike[MAX_COMMAND_KEY_NUM] = {KEY_D, KEY_D, KEY_W, 0}; //軌道精準攻擊

//工程灣
const byte keycmd_grenade_launcher[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_A, KEY_W, KEY_A, KEY_S, 0}; //榴彈發射器
const byte keycmd_guard_dog_rover[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_W, KEY_A, KEY_W, KEY_D, KEY_D, 0}; //護衛犬漫遊者 ↓ ↑ ← ↑ → →

//機器廠房
const byte keycmd_mortar_sentry[MAX_COMMAND_KEY_NUM] = {KEY_S, KEY_W, KEY_D, KEY_D, KEY_S, 0}; //哨戒迫擊炮 ↓ ↑ → → ↓


void check_which_key_cmd_to_send(BT_NUM current_BT)
{ //set command to button
  switch (current_BT)
  {
    case BT_1:
      send_key_cmd(keycmd_reinforce); //增援
      last_BT = BT_1;
      break;
    case BT_2:
      send_key_cmd(keycmd_resupply);  //重新補給
      last_BT = BT_2;
      break;
    case BT_3:
      send_key_cmd(keycmd_stalwart);  //盟友
      last_BT = BT_3;
      break;
    case BT_4:
      send_key_cmd(keycmd_flamethrower); //火焰噴射器
      last_BT = BT_4;
      break;
    case BT_5:
      send_key_cmd(keycmd_grenade_launcher);  //榴彈發射器
      last_BT = BT_5;
      break;
    case BT_6:
      send_key_cmd(keycmd_precision_strike);  //軌道精準攻擊
      last_BT = BT_6;
      break;
    case BT_7:
      send_key_cmd(keycmd_380mm_barrage); //軌道380mm高爆彈彈幕 → ↓ ↑ ↑ ← ↓ ↓
      last_BT = BT_7;
      break;
    case BT_8:
      send_key_cmd(keycmd_guard_dog_rover); //護衛犬漫遊者 ↓ ↑ ← ↑ → →
      last_BT = BT_8;
      break;
    case BT_9:
      last_BT = BT_9;
      send_key_cmd(keycmd_mortar_sentry); //哨戒迫擊炮 ↓ ↑ → → ↓
      break;
    case BT_10:
      last_BT = BT_10;
      send_key_cmd(keycmd_eagle_500kg_bomb); //飛鷹500kg炸彈 ↑ → ↓ ↓ ↓
      break;

    case BT_NC:
      last_BT = BT_NC;
      DigiKeyboard.sendKeyStroke(0);
      break;
  }
}

/*****其他程式碼*****/
void check_BT_send_cmd()
{ //Check button and send key command

  BT_NUM current_BT = check_BT_press();

  if ((current_BT == BT_NA) || (last_BT == current_BT))
    return; //No BT change, return

  if ((last_BT != BT_NC) && (current_BT != BT_NC))
  { //release last button
    last_BT = BT_NC;
  }

  check_which_key_cmd_to_send(current_BT);
}

void setup()
{
  //Init var
  last_read_BT = BT_NA;
  last_BT = BT_NC;
  last_read_BT_time = 0;

  //Generate Button voltage table
  Gen_BT_volt_tab();

  //Setting up the internal pull-ups resistors
  //and also setting the pins to inputs.
  pinMode(BT_VOLT_PIN, INPUT);  // analog in
  pinMode(LED_R, OUTPUT);
  //pinMode(pin2, INPUT);
}

void loop()
{
  check_BT_send_cmd();
}

void Gen_BT_volt_tab()
{ //Generate button volt table
  BT_volt[0] = GPIO_VOL * (R2 * TOTAL_BT_NUM) / (R2 * TOTAL_BT_NUM + R1); //BT_NC
  BT_volt[1] = 0; //BT_1
  for (int i = 2; i <= TOTAL_BT_NUM; i++)
    BT_volt[i] = GPIO_VOL * (R2 * (i - 1)) / (R2 * (i - 1) + R1); //BT_2, BT_3...
}

BT_NUM check_BT_press()
{ //Check which button are press
  int value = analogRead( BT_VOLT_PIN );
  float volt = value * GPIO_VOL / 1023.0;
  BT_NUM current_read_BT = BT_NA;

  //Check which button press
  for (int i = 0; i <= TOTAL_BT_NUM; i++)
  {
    if ((volt > BT_volt[i] - BT_volt_offset) && (volt < BT_volt[i] + BT_volt_offset))
    {
      current_read_BT = (BT_NUM) i;
      break;
    }
  }

  //Debounce
  if ((millis() - last_read_BT_time) > deBounce_time)
  { //more than debounce time check status
    last_read_BT_time = millis();
    if (last_read_BT != current_read_BT)
    {
      last_read_BT = current_read_BT;
      return BT_NA;
    }
    else if (last_read_BT == current_read_BT)
    {
      last_read_BT = current_read_BT;
      return current_read_BT;
    }
  }

  return BT_NA;
}

void send_key_cmd(const byte key_cmd[])
{
  //Send key command
  byte previous_key = 0;

  if (key_cmd[0] == 0)
    return; //an empty key cmd

  //flash LED
  LED_blink(LED_R, 50);

  //release all keys at first
  DigiKeyboard.sendKeyStroke(0, 0);
  delay(100);

  //Hold Left Control first
  DigiKeyboard.sendKeyPress(0, MOD_CONTROL_LEFT);
  delay(DELAY1);

  //Start press command keys
  for (int i = 0; i <= MAX_COMMAND_KEY_NUM; i++) {
    if (key_cmd[i] == 0)
      break;  //command end

    if (previous_key == key_cmd[i]) {
      //release key first if the next key is the same key
      DigiKeyboard.sendKeyPress(0, MOD_CONTROL_LEFT);
      delay(DELAY2);
    }

    DigiKeyboard.sendKeyPress(key_cmd[i], MOD_CONTROL_LEFT);
    delay(random(DELAY2, DELAY2 + MAX_RANDOM_DELAY));

    previous_key = key_cmd[i];
  }

  //release all keys
  DigiKeyboard.sendKeyStroke(0, 0);
  delay(DELAY3);
}

void LED_blink(int LED, int time_ms)
{ //Turn on LED for certain time
  digitalWrite(LED, HIGH);
  delay(time_ms);
  digitalWrite(LED, LOW);
}

大家有興趣的可以自己做看看.