陳丙山,侯志偉,張永平,景江鵬
(蘭州石化職業技術大學 電子電氣工程學院,甘肅蘭州,730060)
按鍵是嵌入式應用系統中重要的人機交互工具,能夠更好地實現用戶與系統之間的溝通與交互[1~2]。實現按鍵交互的主要結構方式有獨立按鍵和矩陣鍵盤[3~4],其中獨立按鍵的識別效率很高、硬件連接電路簡單,但每個按鍵都要占用一根I/O口引腳,比較適用于少按鍵的應用場景,對于按鍵數量較多的情況下,為節省I/O口資源,需考慮使用矩陣鍵盤結構形式,矩陣鍵盤由多個行線和多個列線交織組成,廣泛應用于數字密碼鎖、臨時存取柜、電梯控制器等多按鍵場景[5~8]。因此,矩陣鍵盤作為電子設備和儀器裝置的人機交互重要媒介,相應也提高了軟件編程的復雜度,本文針對矩陣鍵盤識別時間長、電路擴展性差、通用性低等問題,詳細介紹了以行列掃描法、行列線反法識別5×4(5 行4 列)矩陣鍵盤的優化方法,并提出了以16 位并行端口I2C 擴展器PCA9555a 為紐帶的矩陣鍵盤識別方法,以滿足相關應用場景中對多按鍵識別的實際應用需求。
矩陣鍵盤與處理器的硬件連接結構有直連法和擴展法兩種方式[9~10],為適用于更多按鍵的應用場景,通常采用并行端口擴展器的方式。識別矩陣鍵盤中某個按鍵按下的位置,主要有掃描法、線反法、中斷法等方式,其識別方法包括獲取行列位置、鍵值編碼、確定鍵號以及延時消抖等。
矩陣鍵盤硬件連接電路如圖1 所示, 主控器STM32F103C8 與5 行4 列矩陣鍵盤的連接關系為:PB7~PB11 分別連接矩陣鍵盤的行線,PB12~PB15 分別連接矩陣鍵盤的列線;VIRTUAL TERMINAL 用來調試輸出過程信息;PA0 為處理器正常工作指示燈。

圖1 掃描法硬件連接電路
根據行線列線的高低電平來識別是否有按鍵按下,從而獲取按鍵值。行列掃描法的識別思路為將列線GPIO 口PB12~PB15 設置為輸出模式,行線GPIO 口PB7~PB11 設置為輸入模式,采用“寫列線,讀行線”的方式逐列掃描,讀行判斷是否有按鍵按下,其主要步驟為:
第一步,置第1 列為低電平,其余列為高電平,那么列線PB12~PB15 的colval 值為1110;
第二步,讀取行線數據,行線有低電平表示該行有按鍵按下,例如當按下的按鍵在第1 行時,第1 行的行線I/O口為低電平,那么行線PB7~PB11 的rowval 值為11110,如表1 所示。將rowval 值按位取反得到行線二進制值為00001(十進制為1)。同理,第2 行的行線二進制值為00010(十進制為2),第3 行的行線二進制值為00100(十進制為8),第4 行的行線二進制值為01000(十進制為16),第5 行的行線二進制值為10000(十進制為32)。

表1 第1列為低電平時讀取的行線值
由此可見,按鍵按下的所在行keyrow與讀取行值rowval按位取反的值呈現的數學關系式為:
式(1)中,C 語言math.h 頭文件里log 函數為基數為e 的對數;
第三步,計算按下按鍵的鍵號keyval,與所在行keyrow的計算同理,按鍵按下的所在列keycol與設置行值colval按位取反的值呈現的數學關系式為:
那么,按鍵按下對應的鍵號keyval為:
第四步,按以上步驟以此類推逐列掃描完所有列。
本文矩陣鍵盤按鍵識別優化算法極大避免了鍵值編碼和查表取值的繁瑣工作。
采用“寫列線,讀行線”的方式需要逐列進行掃描,完成全部列的掃描其識別時間長、效率低。因此,逐行或逐列掃描法較為繁瑣,在實際的嵌入式應用系統中更常用的是線反法。線反法的識別思路為行線輸出,列線輸入,行線置0,列線置1,按鍵按下時,列線被拉低,讀取列線值確定低電平在哪一列;同理,行線輸入,列線輸出,行線置1,列線置0,按鍵按下時,行線被拉低,讀取行線值確定低電平在哪一行;根據按鍵所在的行線與列線,即可唯一確定按鍵所在位置,進而獲取按鍵的鍵值。其主要操作步驟為:
第一步,將行線PB7~PB11 設置為輸出模式,列線PB12~PB15 設置為輸入模式。
設置方式如下:
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)3<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)3<<0 | (uint32_t)3<<4| (uint32_t)3<<8 | (uint32_t)3<<12 | (uint32_t)8<<16 |(uint32_t)8<<20 | (uint32_t)8<<24 | (uint32_t)8<<28);
第二步,行線輸出低電平,列線輸出高電平,若有按鍵按下,則將某條列線被拉低,讀取列線PB12~PB15 的值colval 不再全為高電平,說明有按鍵按下,跳轉到第三步,否則跳轉至第一步等待有按鍵按下;
第三步,按式(2)計算出按鍵所在列的值keycol;
第四步,將行線PB7~PB11 設置為輸入模式,列線PB12~PB15 設置為輸出模式。
設置方式如下:
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)8<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)8<<0 | (uint32_t)8<<4| (uint32_t)8<<8 | (uint32_t)8<<12 | (uint32_t)3<<16 |(uint32_t)3<<20 | (uint32_t)3<<24 | (uint32_t)3<<28);
第五步,行線輸出高電平,列線輸出低電平,讀取行線PB7~PB11 的值rowval。
第六步,按式(1)計算出按鍵所在行的值keyrow;
第七步,按式(3)計算按下按鍵的鍵號keyval。
具體實現程序如下:
uint8_t keypad_scan1(void)
{
uint16_t rowval;
uint16_t colval;
uint8_t keyval = 0;
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)3<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)3<<0 | (uint32_t)3<<4| (uint32_t)3<<8 | (uint32_t)3<<12 | (uint32_t)8<<16 |(uint32_t)8<<20 | (uint32_t)8<<24 | (uint32_t)8<<28);
oKeypadPort = 0xF000;
if(iKeypadPort!=0xF000)
{
delay_n_ms(10);
if(iKeypadPort!=0xF000)
{
oKeypadPort = 0xF000;
colval = (~(iKeypadPort>>12))&(0x000F);
GPIOB->CRL &= 0x0FFFFFFF;
GPIOB->CRL |= ((uint32_t)8<<28);
GPIOB->CRH &= 0x00000000;
GPIOB->CRH |= ((uint32_t)8<<0 | (uint32_t)8<<4 | (uint32_t)8<<8 | (uint32_t)8<<12 | (uint32_t)3<<16 | (uint32_t)3<<20 | (uint32_t)3<<24 | (uint32_t)3<<28);
oKeypadPort = 0x0F80;
rowval = (~(iKeypadPort>>7))&(0x001F);
keyval = log(rowval)/log(2)*4 + (log(colval)/log(2)) + 1;
do{rowval = (iKeypadPort>>7)&(0x001F);}while(rowval!=0x001F);
}
}
return keyval;
}
為節省GPIO 接口資源,或GPIO 接口較少的情況下,采用并行端口擴展器的方式來實現矩陣鍵盤的硬件連接,PCA9555 是一種400kHz 快速I2C 總線的16 位I/O 擴展器,工作電壓為2.3V~5.5V,由兩個8 位配置(輸入或輸出可選)、輸入端口、輸出端口和極性反轉(高電平有效或低電平有效運行)寄存器組成。主控制器寫入I/O 配置位將I/O 啟用為輸入或輸出,每個輸入或輸出的數據均保存在相應的輸入或輸出寄存器中,輸入端口寄存器的極性可借助極性反轉寄存器進行轉換。采用PCA9555 擴展器的矩陣鍵盤硬件連接電路如圖2 所示。

圖2 擴展法硬件連接電路
PCA9555 的從機地址配置如表2 所示。

表2 第1列為低電平時讀取的行線值
表2 中,PCA9555 的從機地址為0x20,那么寫數據地址為(0x20<<1)|0,讀數據地址為(0x20<<1)|1。
矩陣鍵盤按鍵的識別思路與線反法相似,采用I2C 總線配置16 位I/O 擴展器的輸入或輸出,寫輸出端口寄存器值和讀輸入端口寄存器值。
具體實現程序如下:
uint8_t keypad_scan2(void)
{
uint16_t rowval;
uint16_t colval;
uint16_t PortToData;
uint8_t keyval = 0;
PCA9555A_IOConfiguration(0x20, 0xFF00);
PCA9555A_WriteData(0x20, 0xFF00);
PortToData = PCA9555A_ReadData(0x20, 0x01);
rowval = PortToData&0x001F;
if(rowval != 0x001F)
{
delay_n_ms(2);
PortToData = PCA9555A_ReadData(0x20,0x01);
rowval = PortToData&0x001F;
if(rowval != 0x001F)
{
PCA9555A_WriteData(0x20, 0xFF00);
PortToData = PCA9555A_ReadData(0x20, 0x01);
rowval = (~PortToData)&0x001F;
PCA9555A_IOConfiguration(0x20, 0x00FF);
PCA9555A_WriteData(0x20, 0x00FF);
PortToData = PCA9555A_ReadData(0x20, 0x00);
colval = (~PortToData)&0x000F;
keyval = log(rowval)/log(2)*4 + (log(colval)/log(2)) + 1;
do{
PortToData = PCA9555A_ReadData(0x20,0x00);
colval = PortToData&0x000F;
}while(colval!=0x000F);
}
}
return keyval;
}
為了實驗的便捷和成本,5×4(5 行4 列)矩陣鍵盤的硬件設計思路和軟件識別算法經過仿真軟件Proteus 8.16 SP3(Build 36097) 和 開 發 環 境STM32CubeIDE 1.14.0的聯合調試,驗證了該設計過程的正確性。其中主控器STM32F103C8 的OSC frequency 設置為72MHz。
仿真運行時Virtual Terminal 輸出的結果如圖3 所示。

圖3 仿真運行結果
本文針對矩陣鍵盤識別時間長、電路擴展性差、通用性低等問題,提出了一種矩陣鍵盤行列掃描法、行列線反法識別5×4(5 行4 列)矩陣鍵盤的優化識別算法,極大地避免了鍵值編碼和查表取值的繁瑣工作。行列掃描法將列線設置為輸出模式,行線設置為輸入模式,采用“寫列線,讀行線”的方式逐列掃描,讀行判斷是否有按鍵按下,采用基數為e的對數函數計算出按鍵所在行和所在列,從而計算出按鍵鍵號。但逐行或逐列掃描法較為繁瑣,掃描識別時間長、效率低,進一步改進為線反法:行線輸出,列線輸入,讀取列線值確定低電平在哪一列;行線輸入,列線輸出,讀取行線值確定低電平在哪一行;根據按鍵所在的行線與列線確定按鍵所在位置,進而獲取按鍵的鍵值。為節省GPIO 接口資源,或GPIO 接口較少的情況下,設計了以16 位并行端口I2C擴展器PCA9555a 為紐帶的矩陣鍵盤硬件連接和識別方法。多次實驗結果表明,本文線反法優化識別算法穩定可靠、簡潔清晰、通用性好以及效率更高,擴展法比較適用于GPIO接口資源稀少情況,具有較好的實用價值。