2026年7月29日 星期三

6 Button RP2040-Zero Macro Pad

 3D printer Case

https://www.printables.com/model/1559069-6-button-rp2040-zero-macro-pad/comments






Software Design Specification

(1)RP2040-Zero pinout definitions are as follows:
    #define KEY1     8
    #define KEY2     9
    #define KEY3     10
    #define KEY4     11
    #define KEY5     12
    #define KEY6     13
    #define KEY7     7
    #define PUSH_SW  6
    #define INTA     5
    #define INTB     4
    #define RGB_LED  14


(2)The button definitions are as follows:
    KEY6 = Switch Mode, Mode = 0 to 3.
    
    Mode 0 :
    KEY1 = WIN+d
    KEY2 = win+r -> wait 500ms -> "devmgmt.msc" -> enter -> wait 2000ms -> enter
    KEY3 = win+r -> wait 500ms -> "calc.exe" -> enter
    KEY4 = enter key
    KEY5 = "PassWord" + enter key
    Rotary encoder push SW = PAUSE/PLAY
    Rotary encoder right turn = volume up
    Rotary encoder left turn = volume down
    
    Mode 1 :
    KEY1 = CTRL+X
    KEY2 = CTRL+C
    KEY3 = CTRL+V
    KEY4 = CTRL+Z
    KEY5 = CTRL+Y
    Rotary encoder push SW = PAUSE/PLAY
    Rotary encoder right turn = volume up
    Rotary encoder left turn = volume down
    
    Mode 2 :
    KEY1 = left key
    KEY2 = down key
    KEY3 = right key
    KEY4 = mouse button left
    KEY5 = up key
    Rotary encoder push SW = PAUSE/PLAY
    Rotary encoder right turn = mouse wheel down
    Rotary encoder left turn = mouse wheel up
    
    Mode 3 :
    KEY1 = '<' key
    KEY2 = key keypad subtract
    KEY3 = '>' key
    KEY4 = WIN+d
    KEY5 = key keypad add
    Rotary encoder push SW = PAUSE/PLAY
    Rotary encoder right turn = volume up
    Rotary encoder left turn = volume down

(3)RGB_LED color 
    Mode 0 = The breathing light breathes approximately every 2 seconds, changing color with each breath. 
             The colors cycle through R, R+G, G, G+B, B, B+R. 
    Mode 1 = all color (R)
    Mode 2 = all color (G)
    Mode 3 = all color (B + 30%G)

Arduino Code:

/*
 * 6Key_3D_case.ino
 * Waveshare RP2040-Zero  +  6 keys + rotary encoder + WS2812B x8
 *
 * Board  : Raspberry Pi Pico / RP2040 (earlephilhower arduino-pico core)
 * USB    : Tools -> USB Stack -> "Adafruit TinyUSB"  (or "Pico SDK")
 * Libs   : Adafruit NeoPixel
 */

#include <Keyboard.h>
#include <Mouse.h>
#include <Adafruit_NeoPixel.h>
#include <math.h>

// ---------------- Pin map ----------------
#define KEY1        8
#define KEY2        9
#define KEY3        10
#define KEY4        11
#define KEY5        12
#define KEY6        13     // Mode switch
#define KEY7        7      // reserved
#define PUSH_SW     6      // encoder push
#define INTA        5      // encoder A
#define INTB        4      // encoder B
#define RGB_LED     14

#define NUM_LEDS       8
#define LED_BRIGHTNESS 80        // 0-255 overall brightness cap
#define DEBOUNCE_MS    20

Adafruit_NeoPixel strip(NUM_LEDS, RGB_LED, NEO_GRB + NEO_KHZ800);

// ---------------- Globals ----------------
uint8_t mode = 0;                            // 0..3

const uint8_t keyPins[6] = { KEY1, KEY2, KEY3, KEY4, KEY5, KEY6 };
bool          keyState[6]      = { HIGH, HIGH, HIGH, HIGH, HIGH, HIGH };
unsigned long keyLastChange[6] = { 0, 0, 0, 0, 0, 0 };

bool          pushState      = HIGH;
unsigned long pushLastChange = 0;

// Rotary encoder (quadrature, interrupt-driven)
volatile int8_t  encoderDelta = 0;
volatile uint8_t encoderLastAB = 0;

// LED breathing
unsigned long modeStartMs = 0;
uint8_t       colorIndex  = 0;

// ---------------- Forward decl ----------------
void handleEncoderISR();
void updateLED();
void handleKeyPress(uint8_t idx);
void handlePushClick();
void handleEncoderStep(int8_t dir);

// ---------------- Helpers ----------------
static inline void tapMod(uint8_t mod, uint8_t key) {
  Keyboard.press(mod);
  Keyboard.press(key);
  delay(30);
  Keyboard.releaseAll();
}

static inline void tapWinKey(uint8_t key) { tapMod(KEY_LEFT_GUI,  key); }
static inline void tapCtrlKey(uint8_t key){ tapMod(KEY_LEFT_CTRL, key); }

static inline void tapShiftKey(uint8_t key) { tapMod(KEY_LEFT_SHIFT, key); }

// ---------------- Setup ----------------
void setup() {
  for (uint8_t i = 0; i < 6; i++) pinMode(keyPins[i], INPUT_PULLUP);
  pinMode(KEY7,    INPUT_PULLUP);
  pinMode(PUSH_SW, INPUT_PULLUP);
  pinMode(INTA,    INPUT_PULLUP);
  pinMode(INTB,    INPUT_PULLUP);

  encoderLastAB = (digitalRead(INTA) << 1) | digitalRead(INTB);
  attachInterrupt(digitalPinToInterrupt(INTA), handleEncoderISR, CHANGE);
  attachInterrupt(digitalPinToInterrupt(INTB), handleEncoderISR, CHANGE);

  Keyboard.begin();
  Mouse.begin();

  strip.begin();
  strip.setBrightness(LED_BRIGHTNESS);
  strip.clear();
  strip.show();

  modeStartMs = millis();
}

// ---------------- Main loop ----------------
void loop() {
  unsigned long now = millis();

  // ---- 6 keys ----
  for (uint8_t i = 0; i < 6; i++) {
    bool s = digitalRead(keyPins[i]);
    if (s != keyState[i] && (now - keyLastChange[i]) > DEBOUNCE_MS) {
      keyLastChange[i] = now;
      keyState[i] = s;
      if (s == LOW) handleKeyPress(i);       // pressed
    }
  }

  // ---- Encoder push ----
  bool p = digitalRead(PUSH_SW);
  if (p != pushState && (now - pushLastChange) > DEBOUNCE_MS) {
    pushLastChange = now;
    pushState = p;
    if (p == LOW) handlePushClick();
  }

  // ---- Encoder rotation ----
  noInterrupts();
  int8_t d = encoderDelta;
  encoderDelta = 0;
  interrupts();
  // Full detent = 4 quadrature steps (typical mechanical encoder)
  static int8_t acc = 0;
  acc += d;
  while (acc >=  4) { handleEncoderStep(+1); acc -= 4; }
  while (acc <= -4) { handleEncoderStep(-1); acc += 4; }

  // ---- LED ----
  updateLED();
}

// ---------------- Encoder ISR (quadrature decode) ----------------
void handleEncoderISR() {
  uint8_t ab   = (digitalRead(INTA) << 1) | digitalRead(INTB);
  uint8_t comb = (encoderLastAB << 2) | ab;
  switch (comb) {
    case 0b0001: case 0b0111: case 0b1110: case 0b1000: encoderDelta++; break;
    case 0b0010: case 0b1011: case 0b1101: case 0b0100: encoderDelta--; break;
    default: break; // invalid / no change
  }
  encoderLastAB = ab;
}

// ---------------- Key actions ----------------
void handleKeyPress(uint8_t idx) {
  // idx 5 == KEY6 : switch mode
  if (idx == 5) {
    mode = (mode + 1) & 0x03;
    modeStartMs = millis();
    colorIndex  = 0;
    return;
  }

  switch (mode) {

    // ============ Mode 0 ============
    case 0:
      switch (idx) {
        case 0: tapWinKey('d'); break;                    // KEY1  Win+D
        case 1:                                           // KEY2  devmgmt.msc
          tapWinKey('r');
          delay(500);
          Keyboard.print("devmgmt.msc");
          Keyboard.write(KEY_RETURN);
          delay(2000);
          Keyboard.write(KEY_RETURN);
          break;
        case 2:                                           // KEY3  calc.exe
          tapWinKey('r');
          delay(500);
          Keyboard.print("calc.exe");
          Keyboard.write(KEY_RETURN);
          break;
        case 3: Keyboard.write(KEY_RETURN); break;        // KEY4  Enter
        case 4:                                           // KEY5  PassWord + Enter
          Keyboard.print("PassWord");
          Keyboard.write(KEY_RETURN);
          break;
      }
      break;

    // ============ Mode 1 ============
    case 1:
      switch (idx) {
        case 0: tapCtrlKey('x'); break;
        case 1: tapCtrlKey('c'); break;
        case 2: tapCtrlKey('v'); break;
        case 3: tapCtrlKey('z'); break;
        case 4: tapCtrlKey('y'); break;
      }
      break;

    // ============ Mode 2 ============
    case 2:
      switch (idx) {
        case 0: Keyboard.write(KEY_LEFT_ARROW);  break;
        case 1: Keyboard.write(KEY_DOWN_ARROW);  break;
        case 2: Keyboard.write(KEY_RIGHT_ARROW); break;
        case 3: Mouse.click(MOUSE_LEFT);         break;
        case 4: Keyboard.write(KEY_UP_ARROW);    break;
      }
      break;

    // ============ Mode 3 ============
    case 3:
      switch (idx) {
        case 0: tapShiftKey(',');            break;   // '<'
        case 1: Keyboard.write(KEY_KP_MINUS);break;   // keypad -
        case 2: tapShiftKey('.');            break;   // '>'
        case 3: tapWinKey('d');              break;   // Win+D
        case 4: Keyboard.write(KEY_KP_PLUS); break;   // keypad +
      }
      break;
  }
}

// Encoder push (all modes): Play / Pause
void handlePushClick() {
  Keyboard.write(KEY_PLAY_PAUSE);
}

// Encoder rotate: dir = +1 (CW / right) or -1 (CCW / left)
void handleEncoderStep(int8_t dir) {
  if (mode == 2) {
    // Mouse wheel: right = wheel down (negative), left = wheel up (positive)
    Mouse.move(0, 0, (dir > 0) ? -1 : +1);
  } else {
    Keyboard.write((dir > 0) ? KEY_VOLUME_INCREMENT : KEY_VOLUME_DECREMENT);
  }
}

// ---------------- LED ----------------
void updateLED() {
  static unsigned long lastShow = 0;
  unsigned long now = millis();
  if (now - lastShow < 20) return;             // ~50 Hz refresh
  lastShow = now;

  uint32_t color = 0;

  switch (mode) {

    case 0: {
      // Breathing ~2 s per breath, cycle color each breath
      const unsigned long PERIOD = 2000;
      unsigned long elapsed = now - modeStartMs;
      if (elapsed >= PERIOD) {
        modeStartMs = now;
        colorIndex  = (colorIndex + 1) % 6;
        elapsed = 0;
      }
      float ph = (float)elapsed / (float)PERIOD;               // 0..1
      uint8_t v = (uint8_t)(sinf(ph * (float)PI) * 255.0f);    // 0..255..0
      uint8_t r = 0, g = 0, b = 0;
      switch (colorIndex) {                                    // R, R+G, G, G+B, B, B+R
        case 0: r = v;               break;
        case 1: r = v; g = v;        break;
        case 2:        g = v;        break;
        case 3:        g = v; b = v; break;
        case 4:               b = v; break;
        case 5: r = v;        b = v; break;
      }
      color = strip.Color(r, g, b);
      break;
    }

    case 1: color = strip.Color(255,   0,   0); break;         // Red
    case 2: color = strip.Color(  0, 255,   0); break;         // Green
    case 3: color = strip.Color(  0,  77, 255); break;         // Blue + 30% Green
  }

  for (uint8_t i = 0; i < NUM_LEDS; i++) strip.setPixelColor(i, color);
  strip.show();
}

2022年9月7日 星期三

巨集鍵盤/自訂義鍵盤 (Macro Keyboard)

  看到網路上有自製巨集鍵盤,心動之下也自己動手做一個專屬自己用的巨集鍵盤。我的目標是不用自己設計PCB,用最簡單的方式來完成巨集鍵盤。所以這個巨集鍵盤全部都是網路買的零件,然後自己手工組裝起來。再花了二、三天的時間去寫軟體(不是full-time,使用上有問題才去改程式),逐步調適完善所以有功能。

  一開始設計的時候先用3x3+1做一個原型機,結果發現不太夠用,可能是我太貪心,將工作、休閒的功能都包括進來。後來改版成4x4個按鍵。



  鍵盤可以分為四個單元:(1)鍵盤定義(2)OLED顯示器(3)背光(4)鍵帽。每個單元說明如下:

(1)鍵盤定義:在鍵盤4x4個按鍵,其中4個固定鍵,12個可變換功能的巨集鍵,共有5套的功能巨集鍵。

(2)OLED顯示器:OLED顯示器有二種規格可以選0.91吋(128x32 pix)及0.96吋(128x64 pix),最後因為壓克力試軸器的尺寸,選擇使用0.91吋OLED顯示器,來作為鍵盤定義鍵的提示。

(3)背光:現成可選擇的有:8x8矩陣型RGB LED、16個環形RGB LED、12個環形RGB LED、8個環形RGB LED。也是因為壓克力試軸器的尺寸及軟體撰寫難易度,所以選擇16個環形RGB LED。在背光顯示方面,最後因為記憶體空間問題,只能做到有4種模式。

(4)鍵帽:在電競機械鍵盤的鍵帽,在不同排的高度有不同,要看個人如何選擇。一套鍵盤的鍵帽,可拆成2組來用,一個是有幅度的,一個是平面式。


  最後來觀看其中一種背光效果,因為不是專業拍攝,顯示螢幕出來的,跟實際看到的還是有一點點的差別。






2020年3月28日 星期六

打燈的紙雕作品


  小弟今天要來設計一個可以打燈的紙雕作品,有點類似相框裡面放上紙雕作品,然後紙雕作品背後打上燈光。來~直接看過程和成品照片。

固定LED燈


黏上紙雕作品(尚未完成)


打開LED燈,觀看其效果如何?(照片上LED燈有點過亮)

關閉背景燈

黏上邊框,作品完成了。

作品的背面。

調了好久,這張照片拍的比較接近真實發光的樣子。
(沒有關閉背景燈)


簡單的藝術燈


  今天要來做一個簡單的藝術燈,首先將雕刻好的A4影印紙裁切成你設計的大小,然後捲起來黏好形成燈罩最外層。再將有顏色的A4影印紙也要裁切,跟剛剛雕刻好的燈罩略小(扣除黏合區)。把有顏色的內襯捲起來放到燈罩裡面。這樣就完成了,是不是很簡單啊?!而且可以隨心情更換內襯顏色。當然要將放入裝有LED銅線燈的玻璃瓶拿來打燈。這次使用LED銅線燈是純白色的。

  下面的照片全部都是左邊是不開LED燈,右邊打開LED燈並且關閉背景燈。 不囉嗦來看成品照片。首先是我推薦的粉粉的淺藍。



  接下來是淺淺的草綠色。


  最後是粉紅色。


  從以前到現在拍了這麼多照片,不管再麼拍,只要遇上打燈,總是覺得拍出的效果都沒有眼睛看的真實,效果很不理想,不是有色差就是局部太亮,不知道是手機太爛,還是我的技術差。唉~~~

2020年3月26日 星期四

LED燈箱(4)

第四款 LED 燈箱

  想用不同顏色的海報紙來做燈箱,先選了一個顏色較深的黃色海報紙,並且用了新圖案來完成新款的燈箱。當在購買海報紙的時候,就在想 “要用什麼顏色的 LED 燈來搭配黃色海報紙呢?” 。目前只是將燈箱放上不同顏色的 LED 燈座來看效果,最後選定以後才會黏合燈座。

首先來看傳統的 “暖白色 LED” 打燈的效果。


LED 燈未開

開啟 “暖白色 LED 燈” ,背景燈也是開的。

關閉背景燈。

再來看看用 “粉紅色 LED 燈” 的效果。

開啟 粉紅色 LED 燈,背景燈也是開的。
 
關閉背景燈。

  由上面的照片來看,你會選用哪種顏色 LED 燈呢?



LED燈箱的製作過程 - 燈箱篇

終於有空來寫燈箱篇了。看看我是如何製作燈箱部分。










2020年3月22日 星期日

如何讓LED燈箱基座瓦楞紙盒可以做打開的活動設計?

如何讓LED燈箱基座瓦楞紙盒可以做打開的活動設計?

  有時候需要對LED燈箱基座瓦楞紙盒作內部處理,例如:放置行動電源、打開電池開關、焊接接線、...。我的作法有二種:

(1)利用市面上販售的磁鐵來做黏合。因為磁吸力超級強,只要對角放置即可(二組,共四個磁鐵)。


(2)利用小磁鐵+迴紋針。目前我的小磁鐵是來自於廢棄小喇叭拆下來的。因為磁力較弱,所以需要四個角落都放置。