從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (5) 製作右手

此為系列文,目前已完成的文章列表:

  1. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (0)
  2. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (1) 製作左手
  3. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (2) 設定 BLE
  4. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (3) Debounce
  5. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (4) 定義 keyboardMgr class
  6. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (5) 製作右手
  7. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (6) 基礎鍵盤
  8. 從 0 開始,手打一把專屬的藍牙雙模人體工學鍵盤 (7) 軌跡球

到今天為止,我們終於真的要開始對右手動工了! 首先我們要先了解右手到底有什麼元件,而這些元件是怎麼跟右手的控制器互動的:

這張圖看起來有點亂,我們一一來解釋。

首先是右 HM-10,他的功能就是接收來自左手的 HM-10 送來的訊號,然後再傳給 Arduino,而我們在前面的文章就已經讓大家見過這方面的實作細節,因此相較來說是比較清楚的。下面的 Bluetooth HID 則單純的接收 Arduino Pro Micro 送過來的按鍵事件,然後轉成藍牙按鍵事件送給電腦,因此 Arduino Pro Micro 並不接收來自 Bluetooth HID 的事件,他只做傳送。下一個是 Blackberry mini trackball,Arduino Pro Micro 要定時去偵測看看是否有新的軌跡球事件。接下來大家會看到一個神奇的東西,叫做 MCP23018,他的角色只是擴充 GPIO,然後這些擴充出來的 GPIO 就可以接上右手自己的 keyboard matrix。為什麼我們在這邊需要擴充 GPIO 呢? 原因當然是因為 pin 腳不夠用啦 ~ 首先是右邊的 HM-10,他需要 2 根 GPIO 進行 UART 協定的溝通,bluetooth HID 也需要 2 根 GPIO 進行UART 協定的溝通。Blackberry mini trackball 在偵測方向上需要 4 根 GPIO,然後另外還可以有四根去控制上面的 LED。最後 keyboard matrix 還需要 13 根,因此整個計算下來是不夠的,所以我們才要接上 MCP23018。 MCP23018 只需要使用 Arduino 上面的 2 根 pin 腳,就可以擴充出新的 16 根 pin 腳。

MCP23018

當然這類擴充 Arduino GPIO 腳的裝置也不少,我們這邊選用 MCP23018 的原因是因為 Ergodox 本來的 PCB 上面就有設計給 MCP23018 的位置,因此直接套用是最容易的。但 MCP23018 剛接觸的人候會讓人有點頭痛,所以在這裡我們要花一些篇幅簡單介紹一下怎麼用這個東西。

首先因為上面本來就有 MCP23018 的位置,所以我們可以把它焊上去

這是右手的正面照,當然和第一篇在講解 keyboard matrix 的時候相同,也要把 1N4188 焊上去喔 (二極體的方向可以參考第一篇的教學指示)
我們直接把 MCP23018 焊到右手的背面上面
配合本來 Ergodox 的設計,MCP23018 晶片的上面 (有凹槽的端) 要跟 PCB 上面的圖示相同,也可以參考我上面焊完的成品

然後要與這張晶片溝通,我們使用的協定叫做 I2C,這個會在稍後介紹,但大家要先在 PCB 上面的這個位置焊上兩個 2.2k 歐姆的電阻

I2C

到底為什麼只需要用兩根 pin 腳,就可以擴充出 16 根 pin 腳呢? 這是因為我們定義了一些訊號的傳過來的時候,我們要怎麼解讀,並且把我們想要進行的控制,也用一些約定好的方式傳過去,這樣 MCP23018 就能夠讀懂我們要做的事情,然後實現了擴充 GPIO。在這裡 MCP23018 是使用 I2C 協定,我在這邊會對 I2C 做非常粗淺的介紹,關於裡面到底是怎麼實現的,可以參考這篇文章中的 I2C Bus 協定部分。

如果你有某些裝置支援 I2C 的話,我們可以把這些裝置上面標明 SCL 跟 SDA 的兩根 pin 腳,都和 Arduino 的 SCL 和 SDA pin 腳互接,你沒看錯,所有人的 SCL 跟 SDA 是接在一起的。而理論上僅用這兩根 pin 腳所能接的最大裝置數量是 127 個! (如果僅使用 7 個 bit 作為定址的話,也有使用 8 bit, 10 個 bit 作為定址的,當然能接的裝置數量就更多)。一個用 I2C 搭成的系統中,會有一個裝置,也僅能有一個裝置是 master,其他接在此 I2C 上的剩餘裝置都只能當作 slave,此外每個裝置都會有一個獨一無二的位址。

I2C 從很抽象的角度來看的話,發生的過程如下:

  1. master 會打開 I2C 通道並做出一個廣播的動作,向整個 I2C 系統廣播一個 address。
  2. 因為所有 slave 都接在這個系統中,因此所有人都會收到這個廣播。收到廣播後會比對看看廣播的 address 和自己的是否相同,如果相同則表示 master有事要找他;反之則就忽略下面在 I2C bus 上面的資料傳輸。
  3. master 會送出定義好的訊號,例如控制 GPIO,讀取 GPIO 等
  4. 被 master 找的 slave 收到這些訊號以後,就可以依照 master 的要求做事。
  5. master 關閉 I2C 通道。

透過以上這五個步驟,大家應該不難想像為什麼前面 I2C 會提到定址這件事情,以及他為什麼只靠兩根 pin 腳就可以達到跟一大堆的裝置溝通。那為什麼需要那兩根電阻呢? 這是因為在前面的抽象步驟中,我們有提到 “打開 I2C” 通道這件事。這件事是使用改變電壓達到,只要所有在 I2C 線上的裝置發現到電壓改變了,就曉得 master 打開通道要跟大家溝通,那麼如同我們在第一篇文章中講到的 pull-up resistance,I2C 為了要避免訊號有扭曲,floasting 等等的現象,因此接了一組上拉電阻。

I2C 解釋到這邊以後,我們就要開始實際操作 MCP23018 了! 首先大家可以看 MCP23018 的 datasheet,然後把 Arduino Pro Micro 的 SCL 跟 SDA 腳接上 (SCL 跟 SDA 在 Arduino Pro Micro 上分別是 pin 3 和 pin 2)。

可以先不要急著焊上去,我們先用杜邦線這樣插著做一些檢查

測試連線

跟之前我們焊接的過程相同,在我們真的把一些比較難解焊的部份焊上去之前,我們都要先做一些檢查。第一個要檢查的就是 Arduino Pro Micro 能不能和 MCP23018 正確的做溝通。我從 Arduino 網站上的這個範例開始,
做了一點修改後變成了 5_right_hand_i2c/i2c_scanner 這個範例。我們把這個範例燒上去 Arduino Pro Micro 後,打開 serial monitor,就會看到這樣的文字

題外話,上面寫 /dev/cu.usbmodem1D131 (Arduino Leonardo),會寫 Leonardo 就是因為我這片是 5V 的 Pro Micro,然後燒了 Arduino Leonardo 的 bootloader 進去

如果你成功的掃描到 MCP23018 在 0x20 的位址,那你和 MCP23018 之間的連接是沒問題的 (題外話,關於為什麼 MCP23018 的位址是 0x20,以及是否能改變位址,聰明的你可以在上面的 MCP23018 datasheet 裡面找到答案)。

測試 Keyboard Matrix

確定了你手上的 MCP23018 運作正常,Arduino 也能夠和他溝通以後,我們就可以做下一步的檢查。下一步要檢查的和第一篇文章一樣,我們要來檢查 keyboard matrix 是否正常。那我們就要在這裡簡介一下 MCP23018 要怎麼溝通。

根據 MCP23018 的 datasheet 中,他有非常多個 8 bit 的 register,透過修改這些 register,我們就可以讓上面不同的腳變成 input,output,或把 pull-up resistor 接到不同的腳上面。因此根據 datasheet,然後配合 5_right_hand_i2c/mcp23018_matrix 我幫大家寫的測試 keyboard matrix 範例,就可以一一測試 keyboard matrix 是否有問題了。

在上面的範例中,我們可以看到 void setup 的函數裡面我們做了以下的事情:

// set group A pins
// begin transmission with mcp23018 at address 0x20
Wire.beginTransmission(0x20);
// select register IODIRA
Wire.write(0x00);
// set 0b00000000 means all pins are set as output
Wire.write(0x00);
Wire.endTransmission();
// set group B pins
// begin transmission with mcp23018 at address 0x20
Wire.beginTransmission(0x20);
// select register IODIRB
Wire.write(0x01);
// set 0b11111111 means all pins are set as input
Wire.write(0xFF);
Wire.endTransmission();
// wire pull up resisters to all B pins
// begin transmission with mcp23018 at address 0x20
Wire.beginTransmission(mcp23018);
// select register GPPUB
Wire.write(0x0D);
// set 0b11111111 means all resisters are connect to IODIRB
Wire.write(0xFF);
Wire.endTransmission();

大家可以看到我們不斷的在重複:

  • Wire.beginTransmission: 打開 I2C 通道,廣播要溝通的 address
  • Wire.write 某個 register: 這是與 MCP23018 溝通好的傳輸方式,要先選定一個 register
  • Wire.write 某個數值: 在告知 MCP23018 是哪一個 register 以後,把想做的控制寫入該 register
  • Wire.endTransmission: 關閉 I2C 通道

在這邊可以看到兩件事,分別是我們前面講的 I2C 抽象的運作模式 (廣播 -> 下指令 -> 關閉通道),第二個是 MCP23018 一直被控制的都是 register 而已。關於控制 register 這件事,我們在這邊留下一個伏筆,在未來的 post 中我們還會再次碰到,大家在此有個模糊的印象即可。

然後在 void loop 裡面,如同 keyboard matrix 一樣,我們都會先 select 一個 row,然後開始一一去讀取每個開關是否有被開啟。在這邊我們是用 MCP23018 的 B 群的 pins 去做讀取的,我們也遵循 MCP23018 定義的傳輸協定,先向 MCP23018 送出一個讀取的訊號,然後接下來就可以接到 MCP23018 一次把 B 群所有 pin 的高低訊號用 8 個 bit 送回來,然後我們在針對這 8 個 bit 做讀取就可以知道按鈕是否有被打開。

如果你的 keyboard matrix 也沒有問題的話,那我們可以準備把右手的電路焊起來了!

因為右手要接的線有點混亂,所以我用洞洞板配上排母,先做了一個簡單的底版出來
然後 Arduino Pro Micro 也焊上排針,這樣就可以直接扣上去
當然為了要能夠放上去,我在右手的上蓋上面也鑽了孔
這樣 Arduino Pro Micro 就可以放上去,而且不小心燒壞 bootloader 的話也可以拔起來重新燒錄

因為等等就要整個焊起來,所以我們要先把腳位定義先寫出來給大家看,然後有不少條跳線要焊

腳位定義:

  • Arduino Pro Micro pin 2 -> MCP23018 SDA
  • Arduino Pro Micro pin 3 -> MCP23018 SCL
  • Arduino Pro Micro pin 4 -> trackball right pin
  • Arduino Pro Micro pin 5 -> trackball red led
  • Arduino Pro Micro pin 6 -> trackball blue led
  • Arduino Pro Micro pin 7 -> HM-10 Tx pin
  • Arduino Pro Micro pin 8 -> HM-10 Rx pin
  • Arduino Pro Micro pin 9 -> trackball green led
  • Arduino Pro Micro pin 10 -> trackball white led
  • Arduino Pro Micro pin 14 -> bluetooth HID Tx pin
  • Arduino Pro Micro pin 15 -> bluetooth HID Rx pin
  • Arduino Pro Micro pin 16 -> trackball up pin
  • Arduino Pro Micro pin A1 -> trackball left pin
  • Arduino Pro Micro pin A2 -> trackball down pin
  • Arduino Pro Micro VCC -> PCB VCC
  • Arduino Pro Micro GND -> PCB GND
  • Arduino Pro Micro RAW -> TP4056 OUT+

基本上就是每一條都要拉跳線啦 XD 可能有些人會問為什麼 pin 腳要這樣分配,我在這邊做個解釋:

  1. 為什麼 trackball 的 pin 腳會不斷的穿插? 因為 Arduino 上面並不是每一根 pin 腳都可以做 PWM (一種可做電壓控制 pin 腳),加上 Arduino Pro Micro 使用的 Atmega 32u4 僅有幾根 pin 腳可作為 UART 的 receive pin,所以才如此混亂
  2. 為什麼不用 Arduino Pro Micro 上面原生的 Tx 跟 Rx 來進行 UART 溝通呢? 因為 Pro Micro 上面僅有四根可作為 interrupt 的 pin 腳,其中兩根被 I2C 用掉了,還有兩根就是 Tx 和 Rx pin。我本來是打算把休眠的功能也做進去的,如果你仔細看 MCP23018 的 datasheet,你會發現它其實還有兩根 pin 腳是能夠被設定成在某些特定情況下,會自動改變這兩根 pin 的電壓的。本來我是想利用這個設計來達到可以休眠,而只要按鍵盤上隨意的按鍵就可以喚醒 Arduino Pro Micro 的。在我自己做的 prototype 上我是有把 MCP23018 的 INT pin 拉到 Pro Micro 的 Rx 上,但後來沒有把這部份做完,所以沒在這裡介紹。
焊完跳線以後會像這樣亂糟糟的,我也把充電模組,滑動開關都放上去了
這是今天最後要完成的樣子,只有軌跡球模組的跳線還拉在外面,其他部分都應該要組裝完成

當我們把按鍵都焊上去以後,我們就可以擴充前一篇引入的 KeyboardMgr。我寫了一個範例,在 5_right_hand_i2c/single_right_keyboard 中,這個範例我先把之前的與 HM-10 溝通的功能拿掉,有助於大家了解 keyboardMgr 如何處理 keyboard matrix,而我們也會在下一講開始把 HM-10 的部分加回來的。