根據tmk製作自己的機械鍵盤固件(一)
譯者註:本文是講解如何使用開源機械鍵盤固件tmk修改得到你需要的鍵盤固件,本人水平有限,所以附上了原文和翻譯後的文字。
This is a tutorial about building your own custom firmware with Hasus keyboard firmware.
本文將介紹如何使用固件Hasus keyboard firmware來構建你自己的鍵盤固件。
It may seem daunting to work with Hasus wall of code but the files you need to modify are not many and you dont actually need to fully understand the code. I myself dont understand 100% of it, so if I write some bullshits I hope Hasu will chime in.
猛地看上去似乎修改Hasu的代碼是十分困難的,但是實際上你需要修改的內容並不會很多並且你也不必知道整個代碼是幹什麼的。我本人也並不能完全百分百明白所有的代碼,所以我寫出了這篇胡言亂語的文章,如果Hasu能夠斧正那就更好了。
Development environment 開發環境
First of all you need to set up the develpment environment. That depends on your system.
首先我們需要構建開發環境,不同的操作系統的構建過程並不相同
On Windows get winavr.
在windows下你需要下載安裝 winavr.
On Mac get CrossPack and from the App Store install XCode.
在mac下你需要CrossPack,並且還需要從app store上下載xcode
On Linux you need to install the development tools (eg: on Ubuntu the package should be called "build-essential") and the AVR dev tools. Again, naming varies based on the distro, search for AVR in your package manager and you should find all you need (eg: for Ubuntu should be "gcc-avr gdb-avr binutils-avr avr-libc avrdude").
在linux下,你需要安裝開發工具(比如在ubuntu上安裝的是「build-essential」的包)還需要安裝AVR開發工具。在此強調,不同的linux發行版對於這些工具的名字都不一樣,在你的發行版中的包管理器中搜索AVR,你就會找到你所需要的工具(比如,在ubuntu上你需要下載安裝的是「gcc-avr gdb-avr binutils-avr avr-libc avrdude」)。
To actually burn the firmware in the controller you need a programmer. You can use Atmels FLIP or dfu-programmer, but I find the easiest to use is the Teensy loader.
在真正將固件燒入控制器中的時候,你依舊需要一個特殊的程序(programmer)(譯者註:或許可以稱之為下載程序)。雖然你可以使用Atmel的 FLIP 或者 dfu-programmer, 但是實際上最簡單的還是使用 Teensy loader這個軟體.
If you cant actually build the firmware yourself, dont worry, you can still make the modifications needed to the code and I bet someone here on the forum can send you the HEX file youll be uploading to the keyboard (you still need to install the programmer above).
如果你並不打算自己製作固件,不用擔心,你依舊可以能夠對這些代碼做一些你想要的修改,我想論壇中的一些人還是很樂意能夠幫你修改之後給你發送HEX文件,這樣你就可以直接將HEX文件燒入控制器中了(當然,你依舊需要前面提到的下載程序軟體)
Get the code 獲取原始的代碼
Download and unzip Hasus code from github. https://github.com/tmk/tmk_keyboard/archive/master.zip
你可以從https://github.com/tmk/tmk_keyboard/archive/master.zip 下載zip文件然後解壓它。
Fortunately you can ignore most of the code there. Go to the keyboard/gh60 directory. We wont never leave that directory, Ive told you it was easy.
幸運的是你只需要了解很少的代碼。現在切換到keyboard/gh60這個文件夾內。我們之後的操作都是在這個文件夾內,是吧,我告訴過你這很簡單。
Remember to check Hasus repository once in a while, it is very active and it often gets updated.注意:要經常查看Hasu的倉庫的修改,因為這個項目現在非常活躍並且經常更新。
Main config 主要的修改
Open Makefile file and check the following globals:
打開Makefile文件並檢查下面子這些內容
TARGET = gh60_lufa......MCU = atmega32u4......#NKRO_ENABLE = yes
if you are using the Teensy++ change the MCU to at90usb1287. Also comment out (place a # at the beginning of the line) the NKRO_ENABLE feature. You dont need it, and it is not supported by LUFA. More about this later.
如果你是使用Teensy++的話,那麼你的MCU將會是at90usb1287。還需要注釋掉NKRO_ENABLE這一項功能(在這一行前面加上一個#即可)。你並不需要這個功能,因為LUFA並不支持這個功能。更多關於這項的內容在後面還會提到。
Now open the config.h file.
現在打開config.h文件
You can give custom values to MANUFACTURER, PRODUCT and DESCRIPTION, but thats completely optional. Whats important is:
你可以選擇性的設置MANUFACTURER, PRODUCT 和 DESCRIPTION這三個的值,但是這些都是不必須的(譯者註:這個或許就是你插上鍵盤之後,在電腦上顯示的鍵盤的名字之類的信息)。下面的才是重要的:
#define MATRIX_ROWS 5#define MATRIX_COLS 14
Change the values to respectively the number of rows and columns of your keyboard.
根據你鍵盤實際的按鍵矩陣的行數和列數進行設定相應的值。(譯者註:關於鍵盤矩陣的內容請查看譯者之前翻譯的GH60作者的一篇文章)
Setting up rows and columns 設置行和列
Now back to the teensy. Write down how you connected the rows/cols (and eventually the capslock LED) to the teensys pins. Remember to avoid pin D6 that is dedicated to the teensys internal LED.
現在,回到teensy(譯者註:這是一個帶最小系統和USB介面的avr的板子,可以在淘寶上買到)中。寫下你如何連接行和列(以及最終的大小寫LED燈)到teensy的引腳上。記住teensy本身的D6被用作驅動內部的LED燈,這不應包括在前面所說的行和列中。(譯者註:譯者認為他是這個意思,請自行閱讀英文去理解)
Just for the sake of this tutorial lets say you have 15 cols and 5 rows. In this example the columns are connected like this:
僅僅為了能夠繼續接下來的文章,在這裡我們假設有15列、5行。在咱們這個例子中這些個列是這麼連接的:
col: 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14pin: F7 B6 B5 B4 D7 C7 C6 D3 D2 D1 D0 B7 B3 B2 B1
Rows look like this instead:
行是這麼連接的:
row: 0 1 2 3 4pin: F0 F1 F4 F5 F6
Remember, this is just an example, your pin configuration might be different.
記住,這只是一個例子而已,你自己的引腳的設置應該是不一樣的。
You can check the name of the pins from PJRC pinout page or reading them directly from the Teensy.
你可以從 PJRC pinout page查看每一個引腳的名字,當然,或者,你還可以直接從teensy中去查找這些個引腳的名字。
Open the matrix.c file. Search for the init_cols function and change it according to your pinout
打開matrix.c這個文件。然後能看到這些代碼:
static void init_cols(void){ // Input with pull-up(DDR:0, PORT:1) DDRF &= ~(1<<7); PORTF |= (1<<7); DDRB &= ~(1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3 | 1<<2 | 1<<1); PORTB |= (1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3 | 1<<2 | 1<<1); DDRD &= ~(1<<7 | 1<<3 | 1<<2 | 1<<1 | 1<<0 ); PORTD |= (1<<7 | 1<<3 | 1<<2 | 1<<1 | 1<<0 ); DDRC &= ~(1<<7 | 1<<6); PORTC |= (1<<7 | 1<<6);}
Its easier than it looks. The name of the pins are composed by one letter and one number from 0 to 7. If you look at the list of columns, I have one pin starting with F and the number for that pin is 7. So to activate that pin I have to add the following to the list:
雖然看上去比較難懂,但是實際比看起來容易懂的多。引腳的名字是由字母和由0到7的數字組成的。如果你回顧上面所說的列的連接情況,你會想起你有一個引腳是F7,所以激活這個引腳就可以用到下面這兩行代碼:
DDRF &= ~(1<<7); PORTF |= (1<<7);
DDRF and PORTF are the hardware registers that control the port F pins. 1<<7 is a bitwise operator that picks the 8th bit of that port (remember we start counting from 0, so the 8th pin is actually number 7).
DDRF和PORTF是控制埠F引腳的硬體寄存器。1<<7是一個位操作,它將選中這個埠的第八個位(記住,我們是從0開始計數的,所以第八個引腳實際上引腳7)。
譯者註:DDRF是埠B的方向寄存器。DDRF 中對應的位為1則表示該引腳是輸出引腳,如果此時向PORTF中對應的位寫入1或者0,則單片機的該引腳就會輸出1或者0;當DDRF中這個位為0,則表示該引腳為輸入引腳,此時讀取PINF中對應位即為該引腳的輸入值。所以這段程序實際的意思是:將DDRF7清零即B組的7號引腳(第八個)設置為輸入,PORTF7設置為1,此外還隱含著一個PUD為0,PUD在寄存器MCUCR中,不過在這裡其實也並不需要知道這個。所以這意味著什麼呢?對於不是沒學過模電的人來說還是很不好理解的,總體來說,如果沒有這個上拉電阻,這個引腳將不能提供電流輸出,很神奇的事情!這裡是說此引腳為輸入引腳,但是卻是提供電流的!這個理論在之前我翻譯的GH60作者的一篇文章中的理論1部分,另外說一下並不需要去看那篇文章了,那篇文章中最終關於矩陣的排列情況似乎和本文的例子是相反的,即:本文是列為輸入,GH60作者的那篇文章行為輸入。但是在這裡並不僅僅用到這個「提供電流」這個特性,而且如果這個引腳不是有上拉電阻就一定是高阻態了,如果是高阻態的話,會使得讀取的數據不穩定,因為列的引腳都會和二極體、按鍵、行引腳連接在一起,如果按鍵沒有按下的時候,二極體無論如何是沒有導通的,這就意味著,此時讀取引腳的值,將會不知道讀取到什麼值。所以這裡將其設置為帶上拉電阻的話,會是得每一次讀取引腳的值不是高電平就是低電平,不會出現高阻態帶來的不確定性,譯者看到這裡根據源代碼和電路特點另加的一點內容,不知道本文的作者會不會在後面詳細講解,先放在這裡吧。譯者註:仍然需要強調一下,GH60的作者的那篇文章和本文稍微有點不一樣,即:本文列是輸入,那個文章行是輸入。但是仔細去看GH60的PCB板子的原理圖會發現,其實實際上GH60的板子列是輸入(一個很明顯的特點是二極體的方向和GH60作者自己講解原理的文章中不一樣)
Lets proceed with the second port.
讓我們繼續講解第二個埠吧
DDRB &= ~(1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3 | 1<<2 | 1<<1); PORTB |= (1<<7 | 1<<6 | 1<<5 | 1<<4 | 1<<3 | 1<<2 | 1<<1);
In this case we are on port B (DDRB and PORTB) and if you look at our cols schema youll see that we are using the pins number B7, B6, B5, B4, B3, B2 and B1 (even if they are not consecutive on the matrix, it doesnt matter). We use | (or) to smartly react to any of the selected pins. 1<<7 means B7, 1<<6 means B6, and so on.
在這裡我們使用埠B(DDRB和PORTB),並且如果你看到前面所說的那些個列,你會發現我們使用了引腳B7、B6、B5、B4、B3、B2、B1(即使他們在實際的矩陣中並不是連續的也沒關係)。我們使用|(或)可以很智能的選擇那些個引腳。1<<7意味著B7,1<<6是指B6,其他的類似。
I believe at this point it should be clear how it works. Proceed with all the other columns.
我想我講的這些已經清晰了,其他的列也是這樣的初始化的。
Cool. We just initiated the columns. Now we have to read them. Search for the read_cols function:
太棒了(譯者註:翻譯腔)。我們初始化了所有的列。現在讓我們去解讀它們。你可以找到read_cols這個函數
static matrix_row_t read_cols(void){ return (PINF&(1<<7) ? 0 : (1<<0)) | (PINB&(1<<6) ? 0 : (1<<1)) | (PINB&(1<<5) ? 0 : (1<<2)) | (PINB&(1<<4) ? 0 : (1<<3)) | (PIND&(1<<7) ? 0 : (1<<4)) | (PINC&(1<<7) ? 0 : (1<<5)) | (PINC&(1<<6) ? 0 : (1<<6)) | (PIND&(1<<3) ? 0 : (1<<7)) | (PIND&(1<<2) ? 0 : (1<<8)) | (PIND&(1<<1) ? 0 : (1<<9)) | (PIND&(1<<0) ? 0 : (1<<10)) | (PINB&(1<<7) ? 0 : (1<<11)) | (PINB&(1<<3) ? 0 : (1<<12)) | (PINB&(1<<2) ? 0 : (1<<13)) | (PINB&(1<<1) ? 0 : (1<<14));}
This function associates each pin to its col. So
這個函數將會將引腳關聯到相應的列。所以
(PINF&(1<<7) ? 0 : (1<<0))
Tells the controller that if pin F7 (PINF...7) is active then we are on the first column (again zero based, 1<<0 means first column).
這就意味著,如果P7被激活了,那麼我們就在第一列(再次強調,這裡是以0為基礎計數的,1<<0表示第一列)。(譯者註:作者好像說錯了,應該是如果P7讀取的是0,那麼就表示這一列中有按鍵被按下了,而且這一列還是第一列)
(PINB&(1<<6) ? 0 : (1<<1))
If pin B6 (PINB...6) is high then we are on the second column (1<<1). And so on. A piece of cake.
譯者註:作者這裡好像說錯了,應該是B6為低電平時表示被選中了,但是他說成了為高電平時表示被選中。
Now to the rows. Search the unselect_rows function.
現在我們看看行吧。尋找到函數unselect_rows
static void unselect_rows(void){ // Hi-Z(DDR:0, PORT:0) to unselect DDRF &= ~0b01110011; PORTF &= ~0b01110011;}
Hasu chose a different syntax here. We are talking binary this time, but its still pretty easy. If you look at the rows schema youll see that we are using pins number 0, 1, 4, 5, 6 of the F port.
Hasu換了一種寫法。這次我們討論一下二進位的寫法,但是這個還是足夠簡單的。如果你回頭看了那個行的設置的時候,你會發現我們是在使用F端引腳的0、1、4、5、6。
So we write to the F port with DDRF and PORTF like we did for the columns but to choose the port number we set it to 1. (actually we are unselecting them, but this is meant for newbie and Im not going deeper into this).
所以我們可以修改DDRF和PORTF中的內容,就像咱們之前修改列那樣,不過,咱們在這裡是把對應引腳的寄存器的位設置成了1.(譯者註:作者在這裡添加了一些小注釋,表示他並不想把這裡的原理講的太清楚,譯者在這裡稍微解釋一下:很簡單,我們需要行是輸出的,所以DDRF設置成這樣,當我們沒選中這行的時候,也就是調用這個函數的時候,我們希望這些個行的引腳是高阻狀態,也就是需要設置PORTF,為什麼這樣設置呢?因為我們之後會通過select_row這個函數選中某一行,這樣每次只會選中一行,不會導致,沒被選中的這一行A的按鈕被按下被錯認為是選中的那一行B對應的那一列確定的那個按鈕被按下。)
0b just tells the compiler that we are talking binary. Then you have 8 zeros and ones. If you look closely youll see that the 1s are --reading from right to left-- in the 1st, 2nd, 5th, 6th, 7th positions, or pin number 0, 1, 4, 5, 6 that are the columns that we are using for our keyboard.
0b只是用來告訴編譯器我們是在使用二進位進行表示數字。我們使用八位表示0和1,。如果你仔細看的話,從右到左依次第一個、第二個、第五個、第六個、第七個位置或者說引腳0、1、4、5、6是連接著鍵盤列的引腳。
Start counting from the right. The first bit is PIN0, the last is PIN7. Just set the pins you use to 1. Peachy, isnt it?
從右到左數,第一個位是PIN0,最後一個是PIN7。只需要將你需要的引腳設為1就行了,很簡單吧。
Now to selecting the rows. Search for the select_row function.
現在讓我們來選擇行吧,尋找到select_row函數:
static void select_row(uint8_t row){ // Output low(DDR:1, PORT:0) to select switch (row) { case 0: DDRF |= (1<<0); PORTF &= ~(1<<0); break; case 1: DDRF |= (1<<1); PORTF &= ~(1<<1); break; case 2: DDRF |= (1<<4); PORTF &= ~(1<<4); break; case 3: DDRF |= (1<<5); PORTF &= ~(1<<5); break; case 4: DDRF |= (1<<6); PORTF &= ~(1<<6); break; }}
At this point it should pretty clear whats going on here.
為了更好的講解,把這些語句分開拿出來講解。
case 0: means: "if we are on the first row".
case 0 表示 「如果我們選中了第一行」
DDRF |= (1<<0);PORTF &= ~(1<<0);
Means: "select port F, bit 0".
表示選中F區並清0.。(譯者註:將相應的引腳設置成了輸出並輸出0。)
Now modify your ports/pins according to your schema.
現在根據你的原理圖修改你的引腳設置吧。
Lastly open the led.c file and change the port to the one you connected the LED to.
最後我們打開led.c文件,並修改連上你LED的埠引腳。
void led_set(uint8_t usb_led){ if (usb_led & (1<<USB_LED_CAPS_LOCK)) { // output low DDRD |= (1<<4); PORTD &= ~(1<<4); } else { // Hi-Z DDRD &= ~(1<<4); PORTD &= ~(1<<4); }}
In this case we have the LED on pin D4. Lets say it were on pin F0 it would have been:
在原例子中將D4連接到了LED上。但是咱們想要的是將F0接到LED上,所以,代碼應該是這樣的
DDRF |= (1<<0);PORTF &= ~(1<<0);
Okay, a lot to digest. Thats enough for the first lesson. In the next session we will be customizing the actual key matrix and the function layers. If you have questions or you spotted some errors, just let me know.
第一課的內容寫成這樣已經足夠了。在下一篇文章中,我們將定製實際的鍵盤矩陣和函數層。如果你有問題或者你發現了錯誤,請一定要讓我知道。
譯者註:原文鏈接:https://deskthority.net/workshop-f7/how-to-build-your-very-own-keyboard-firmware-t7177.html,此為第一部分的翻譯,第二部分將在以後發出來。
PS:紙糊啥時候能支持markdown格式直接寫文章啊,直接從本地粘貼過來之後代碼部分還得自己調整,真是累啊
推薦閱讀:
※如何讓UEFI BIOS支持漢字顯示:漢字編碼與顯示實踐
※UEFI到操作系統的虛擬地址轉換
※西數硬碟固件調試與逆向分析