Keyboard from Scratch: Từ A tới Z
Sau khi kết thúc hai phần trước, chúng ta đã có những kiến thức cơ bản về chiếc bàn phím cơ, không để các bạn đợi lâu, ở phần này chúng ta sẽ thực sự bắt tay vào làm một chiếc bàn phím hoàn chỉnh.
TL;DR: Các bước để build một chiếc bàn phím cơ
- Tham khảo thật nhiều layout, thiết kế của cộng đồng
- Thiết kế cho mình một layout hợp lý
- Đặt mua linh kiện, keycaps, switches, cắt plate nếu cần thiết
- Hàn mạch bằng tay, hoặc thiết kế mạch in rồi mới hàn
- Hàn controller, viết firmware hoặc modify từ các firmware có sẵn như QMK hay TMK
- Done
Trước khi bắt đầu thì các bạn hãy để mình khoe hàng tí đã, chiếc bàn phím bốn chấm không, à nhầm 40% mang tên SnackyMini Keyboard một sản phẩm kết tinh của tinh thần bốn chấm không, của trí tuệ Việt và nền công nghệ tiên tiến của Đế quốc Tư bản Huê Kỳ, cộng với nguồn cung cấp thiết bị kĩ thuật, linh kiện bán dẫn phong phú của người anh lớn Trung Hoa, một sản phẩm của người Việt, dành cho cả người Việt lẫn người không Việt, được nghiên cứu, thiết kế và tối ưu dành riêng cho các software developer, một... ấy ấy ấy, thôi các bạn đừng tắt mà, ở lại đọc tiếp đi mà, mình im rồi đây
và cuối cùng thì bản này bị vứt xó.. Đặc biệt là trên macOS. Đó là chưa kể cơ chế quản lý thư viện footprint và component của nó khá rối. Trước khi bắt tay vào làm thì các bạn nên đọc qua bài hướng dẫn sử dụng của Deskthority, đây là bài viết dễ hiểu nhất mà mình có thể tìm thấy, mặc dù hơi cũ.
Cái khó thứ hai, là bắt đầu như thế nào? một cái switch có những chân nào, cần đục những lỗ nào? khoảng cách giữa các switch là bao xa? để nắm được các thông tin này thì bạn cần phải bới tung các wiki và các diễn đàn về phím cơ lên. Ở đây mình sẽ nói sơ qua, vì các bạn có đọc trước cũng không thể nhớ được cho tới khi các bạn bắt tay vô làm và tự mình đụng phải các vấn đề đó
-
Về chân switch (footprint) và các linh kiện khác, các bạn có thể tải thư viện hỗ trợ cho KiCad tại đây https://github.com/tmk/keyboard_parts.pretty và đây https://github.com/tmk/kicad_lib_tmk. Đồng thời tham khảo bài viết này để biết cách import https://github.com/ruiqimao/keyboard-pcb-guide.
-
Về khoảng cách giữa các chân switch, thì theo tài liệu của hãng Cherry, mỗi switch có kích thước
15.6mm x 15.6mm
, keycaps có kích thước tầm18mm x 18mm
nữa, theo matt3o, khi kết hợp lại thì mỗi một switch1u
nên chiếm một ô có kích thước19.05mm x 19.05mm
.nếu các bạn sử dụng controller khác, có thể các bạn sẽ compile được và không cần phải đâm đầu vào con đường đen tối này. Suy cho cùng, tự viết firmware cũng có cái hay của nó, và mình học được rất nhiều từ việc này. Dưới đây là một vài ghi chép của mình trong quá trình viết, nếu không thực sự quan tâm, các bạn có thể bỏ qua cũng được.
Mãi cho đến khi hoàn thành xong phần hardware thì mình mới bắt tay vào viết firmware hoàn chỉnh (chứ không phải là cái firmware điều khiển 4 nút như trong bài trước). Về cơ bản thì không khác nhiều mấy so với trong bài trước, mình dùng một mảng kiểu
unit8_t
để lưu thông tin về layout bàn phím, đây là một mảng 3 chiều, nó gồm có 2 mảng 2 chiều, mỗi một mảng 2 chiều là một layout:uint8_t keyLayout[][ROWS][COLS] = { // Default layout { { KEY_TAB , KEY_Q , KEY_W , KEY_E , KEY_R , KEY_T , KEY_Y ,... { NULL_KEY , KEY_A , KEY_S , KEY_D , KEY_F , KEY_G , KEY_H ,... { NULL_KEY , KEY_Z , KEY_X , KEY_C , KEY_V , KEY_B , KEY_N ,... { KEY_TILDE, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY,... }, // Fn layout { { KEY_ESC , KEY_1 , KEY_2 , KEY_3 , KEY_4 , KEY_5 , KEY_6 ,... { NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY,... { NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY,... { NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY, NULL_KEY,... } };
Code cho việc chuyển layout bằng phím
Fn
:if (keys[i].code == FN_KEY) { layoutId = FN_LAYOUT; break; } submitLayout(keys, keyLayout[layoutId]);
Một thay đổi nữa so với bài viết trước, đó là thuật toán debounce, sau khi thử nghiệm và tham khảo từ các firmware khác, thì mình quyết định làm đơn giản hơn, giống với các firmware phổ biến như QMK/TMK, là quét từng đợt sau mỗi 15 milli giây, đơn giản đến không đỡ được:
#define DEBOUNCE_DELAY 15 void loop() { unsigned long timeNow = millis(); if (timeNow - lastFrame > DEBOUNCE_DELAY) { lastFrame = timeNow; // Scan key } }
Khi compile và upload firmware vào bàn phím, ban đầu thì mọi thứ có vẻ hoạt động rất trơn tru, nhưng khi mình thử gõ một đoạn nội dung dài vào, thì xảy ra tình trạng mất phím, ví dụ gõ:
Hello I am Huy
thì lại thành ra:
Helo I m Hy
Thế là tốn thêm 1 ngày để debug, nguyên nhân thì hết sức ngớ ngẩn vì trong lúc viết firmware, mình lười và chỉ làm cho cái bàn phím đọc mỗi một phím duy nhất ở một thời điểm, và khi gõ nhanh, ở một thời điểm có thể sẽ có rất nhiều phím được nhấn xuống. Cũng nhờ thế mà lại biết thêm được khái niệm NKRO (n-key roll over), một chức năng giúp cho bàn phím có thể ghi nhận được nhiều phím trong một thời điểm. Đối với các bàn phím sử dụng cổng USB, thì tối đa chi ghi nhận được 6 phím, trong khi đó bàn phím dùng cổng PS/2 thì xả láng.
Việc implement chức năng này cũng không mấy khó khăn, nhờ các hàm
Keyboard.set_key1
,Keyboard.set_key2
,Keyboard.set_key3
,... của Teensyduino. Về ý tưởng thì ở mỗi lần quét, mình tạo một mảng gồm 6 phần tử để lưu lần lượt giá trị các phím nhận được:#define MAXIMUM_STROKES 6 struct Key* readKey() { struct Key* result = (Key*)malloc(MAXIMUM_STROKES * sizeof(struct Key)); int currentFinger = 0; for (int row = 0; row < ROWS; row++) { for (int col = 0; col < COLS; col++) { if (keyPressedAt(row, col)) { result[currentFinger].row = row; result[currentFinger].col = col; if (currentFinger < MAXIMUM_STROKES) currentFinger++; } } } return result; }
Rồi sau đó lần lượt sử dụng các hàm
Keyboard.set_key<x>
để gán giá trị cho từng phím, và gửi đi một lần bằng hàmKeyboard.send_now()
:void submitLayout(struct Key* keys, uint8_t layout[ROWS][COLS]) { int currentFinger = 0; ... for (int i = 0; i < SUPPORTED_STROKES; i++) { int c = layout[pos.r][pos.c]; if (c != NULL_KEY) { setKey(currentFinger, c); currentFinger++; } } ... Keyboard.send_now(); }
Một số bàn phím còn cho phép người dùng tự cấu hình layout bằng phần mềm trên máy tính, xét ở góc độ firmware, điều này không quá khó, chỉ việc chuyển mảng
keyLayout
về một cấu trúc khác có thể lưu được vào bộ nhớ của Teensy, từ đó ta có thể load ra mỗi khi bàn phím được khởi động. Ví dụ lưu vào EEPROM thông qua hàmEEPROM.read()
vàEEPROM.write()
, tuy nhiên cần lưu ý, EEPROM của Teensy 3.2 chỉ có 2KB, hơi ít, nhưng chắc vừa đủ xài. Nhưng nếu đã tự build được firmware, thì chả cần làm vậy cho mất công, chỉ cần customize layout bằng cách customize luôn source codeMấy hôm nay ngồi xem kĩ lại firmware QMK, thấy nó có vài features khá thú vị, như Grave Escape Key, Key Lock, Tap Dance hay Mouse Key,... hôm nào có thời gian mình sẽ ngồi clone lại và viết thêm về chủ đề này.
Chỉ cần chừng đó thứ thì bạn đã có thể tự mình viết được firmware cho chiếc bàn phím của mình rồi. Nếu quan tâm, các bạn có thể tham khảo mã nguồn đầy đủ của firmware lẫn hardware cho bàn phím này tại đây https://github.com/huytd/snackymini-keyboard/
Xin cảm ơn các bạn đã kiên nhẫn đọc đến tận đây (tui nói vậy thôi chứ tui biết bạn scroll xuống đây từ đầu trang, chỉ tốn có 3 giây). Nếu bạn cũng là dân chơi mech, hy vọng bài viết này giúp các bạn hiểu thêm về những chiếc bàn phím tiền triệu mà mình đã mua. Nếu bạn chưa phải là dân chơi, hy vọng bài viết này làm nhụt chí và ngăn bạn lao vào con đường tốn kém này. Nếu đã đọc hết mà vẫn quyết định sẽ mua hoặc tự làm một chiếc bàn phím cơ, thì mình xin chúc mừng và chúc các bạn may mắn luôn.
P/S: Nếu bạn nào đang ở US, thì mình còn dư vài cái PCB, nếu có hứng thú thì cứ PM mình qua Facebook, mình sẽ gửi tặng.
Bài viết này được gõ bằng bàn phím SnackyMini Keyboard