陳丙山,侯志偉,張永平,景江鵬
(蘭州石化職業(yè)技術(shù)大學(xué) 電子電氣工程學(xué)院,甘肅蘭州,730060)
按鍵是嵌入式應(yīng)用系統(tǒng)中重要的人機(jī)交互工具,能夠更好地實(shí)現(xiàn)用戶與系統(tǒng)之間的溝通與交互[1~2]。實(shí)現(xiàn)按鍵交互的主要結(jié)構(gòu)方式有獨(dú)立按鍵和矩陣鍵盤[3~4],其中獨(dú)立按鍵的識別效率很高、硬件連接電路簡單,但每個按鍵都要占用一根I/O口引腳,比較適用于少按鍵的應(yīng)用場景,對于按鍵數(shù)量較多的情況下,為節(jié)省I/O口資源,需考慮使用矩陣鍵盤結(jié)構(gòu)形式,矩陣鍵盤由多個行線和多個列線交織組成,廣泛應(yīng)用于數(shù)字密碼鎖、臨時存取柜、電梯控制器等多按鍵場景[5~8]。因此,矩陣鍵盤作為電子設(shè)備和儀器裝置的人機(jī)交互重要媒介,相應(yīng)也提高了軟件編程的復(fù)雜度,本文針對矩陣鍵盤識別時間長、電路擴(kuò)展性差、通用性低等問題,詳細(xì)介紹了以行列掃描法、行列線反法識別5×4(5 行4 列)矩陣鍵盤的優(yōu)化方法,并提出了以16 位并行端口I2C 擴(kuò)展器PCA9555a 為紐帶的矩陣鍵盤識別方法,以滿足相關(guān)應(yīng)用場景中對多按鍵識別的實(shí)際應(yīng)用需求。
矩陣鍵盤與處理器的硬件連接結(jié)構(gòu)有直連法和擴(kuò)展法兩種方式[9~10],為適用于更多按鍵的應(yīng)用場景,通常采用并行端口擴(kuò)展器的方式。識別矩陣鍵盤中某個按鍵按下的位置,主要有掃描法、線反法、中斷法等方式,其識別方法包括獲取行列位置、鍵值編碼、確定鍵號以及延時消抖等。
矩陣鍵盤硬件連接電路如圖1 所示, 主控器STM32F103C8 與5 行4 列矩陣鍵盤的連接關(guān)系為:PB7~PB11 分別連接矩陣鍵盤的行線,PB12~PB15 分別連接矩陣鍵盤的列線;VIRTUAL TERMINAL 用來調(diào)試輸出過程信息;PA0 為處理器正常工作指示燈。
圖1 掃描法硬件連接電路
根據(jù)行線列線的高低電平來識別是否有按鍵按下,從而獲取按鍵值。行列掃描法的識別思路為將列線GPIO 口PB12~PB15 設(shè)置為輸出模式,行線GPIO 口PB7~PB11 設(shè)置為輸入模式,采用“寫列線,讀行線”的方式逐列掃描,讀行判斷是否有按鍵按下,其主要步驟為:
第一步,置第1 列為低電平,其余列為高電平,那么列線PB12~PB15 的colval 值為1110;
第二步,讀取行線數(shù)據(jù),行線有低電平表示該行有按鍵按下,例如當(dāng)按下的按鍵在第1 行時,第1 行的行線I/O口為低電平,那么行線PB7~PB11 的rowval 值為11110,如表1 所示。將rowval 值按位取反得到行線二進(jìn)制值為00001(十進(jìn)制為1)。同理,第2 行的行線二進(jìn)制值為00010(十進(jìn)制為2),第3 行的行線二進(jìn)制值為00100(十進(jìn)制為8),第4 行的行線二進(jìn)制值為01000(十進(jìn)制為16),第5 行的行線二進(jìn)制值為10000(十進(jìn)制為32)。
表1 第1列為低電平時讀取的行線值
由此可見,按鍵按下的所在行keyrow與讀取行值rowval按位取反的值呈現(xiàn)的數(shù)學(xué)關(guān)系式為:
式(1)中,C 語言math.h 頭文件里log 函數(shù)為基數(shù)為e 的對數(shù);
第三步,計(jì)算按下按鍵的鍵號keyval,與所在行keyrow的計(jì)算同理,按鍵按下的所在列keycol與設(shè)置行值colval按位取反的值呈現(xiàn)的數(shù)學(xué)關(guān)系式為:
那么,按鍵按下對應(yīng)的鍵號keyval為:
第四步,按以上步驟以此類推逐列掃描完所有列。
本文矩陣鍵盤按鍵識別優(yōu)化算法極大避免了鍵值編碼和查表取值的繁瑣工作。
采用“寫列線,讀行線”的方式需要逐列進(jìn)行掃描,完成全部列的掃描其識別時間長、效率低。因此,逐行或逐列掃描法較為繁瑣,在實(shí)際的嵌入式應(yīng)用系統(tǒng)中更常用的是線反法。線反法的識別思路為行線輸出,列線輸入,行線置0,列線置1,按鍵按下時,列線被拉低,讀取列線值確定低電平在哪一列;同理,行線輸入,列線輸出,行線置1,列線置0,按鍵按下時,行線被拉低,讀取行線值確定低電平在哪一行;根據(jù)按鍵所在的行線與列線,即可唯一確定按鍵所在位置,進(jìn)而獲取按鍵的鍵值。其主要操作步驟為:
第一步,將行線PB7~PB11 設(shè)置為輸出模式,列線PB12~PB15 設(shè)置為輸入模式。
設(shè)置方式如下:
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 不再全為高電平,說明有按鍵按下,跳轉(zhuǎn)到第三步,否則跳轉(zhuǎn)至第一步等待有按鍵按下;
第三步,按式(2)計(jì)算出按鍵所在列的值keycol;
第四步,將行線PB7~PB11 設(shè)置為輸入模式,列線PB12~PB15 設(shè)置為輸出模式。
設(shè)置方式如下:
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)計(jì)算出按鍵所在行的值keyrow;
第七步,按式(3)計(jì)算按下按鍵的鍵號keyval。
具體實(shí)現(xiàn)程序如下:
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;
}
為節(jié)省GPIO 接口資源,或GPIO 接口較少的情況下,采用并行端口擴(kuò)展器的方式來實(shí)現(xiàn)矩陣鍵盤的硬件連接,PCA9555 是一種400kHz 快速I2C 總線的16 位I/O 擴(kuò)展器,工作電壓為2.3V~5.5V,由兩個8 位配置(輸入或輸出可選)、輸入端口、輸出端口和極性反轉(zhuǎn)(高電平有效或低電平有效運(yùn)行)寄存器組成。主控制器寫入I/O 配置位將I/O 啟用為輸入或輸出,每個輸入或輸出的數(shù)據(jù)均保存在相應(yīng)的輸入或輸出寄存器中,輸入端口寄存器的極性可借助極性反轉(zhuǎn)寄存器進(jìn)行轉(zhuǎn)換。采用PCA9555 擴(kuò)展器的矩陣鍵盤硬件連接電路如圖2 所示。
圖2 擴(kuò)展法硬件連接電路
PCA9555 的從機(jī)地址配置如表2 所示。
表2 第1列為低電平時讀取的行線值
表2 中,PCA9555 的從機(jī)地址為0x20,那么寫數(shù)據(jù)地址為(0x20<<1)|0,讀數(shù)據(jù)地址為(0x20<<1)|1。
矩陣鍵盤按鍵的識別思路與線反法相似,采用I2C 總線配置16 位I/O 擴(kuò)展器的輸入或輸出,寫輸出端口寄存器值和讀輸入端口寄存器值。
具體實(shí)現(xiàn)程序如下:
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;
}
為了實(shí)驗(yàn)的便捷和成本,5×4(5 行4 列)矩陣鍵盤的硬件設(shè)計(jì)思路和軟件識別算法經(jīng)過仿真軟件Proteus 8.16 SP3(Build 36097) 和 開 發(fā) 環(huán) 境STM32CubeIDE 1.14.0的聯(lián)合調(diào)試,驗(yàn)證了該設(shè)計(jì)過程的正確性。其中主控器STM32F103C8 的OSC frequency 設(shè)置為72MHz。
仿真運(yùn)行時Virtual Terminal 輸出的結(jié)果如圖3 所示。
圖3 仿真運(yùn)行結(jié)果
本文針對矩陣鍵盤識別時間長、電路擴(kuò)展性差、通用性低等問題,提出了一種矩陣鍵盤行列掃描法、行列線反法識別5×4(5 行4 列)矩陣鍵盤的優(yōu)化識別算法,極大地避免了鍵值編碼和查表取值的繁瑣工作。行列掃描法將列線設(shè)置為輸出模式,行線設(shè)置為輸入模式,采用“寫列線,讀行線”的方式逐列掃描,讀行判斷是否有按鍵按下,采用基數(shù)為e的對數(shù)函數(shù)計(jì)算出按鍵所在行和所在列,從而計(jì)算出按鍵鍵號。但逐行或逐列掃描法較為繁瑣,掃描識別時間長、效率低,進(jìn)一步改進(jìn)為線反法:行線輸出,列線輸入,讀取列線值確定低電平在哪一列;行線輸入,列線輸出,讀取行線值確定低電平在哪一行;根據(jù)按鍵所在的行線與列線確定按鍵所在位置,進(jìn)而獲取按鍵的鍵值。為節(jié)省GPIO 接口資源,或GPIO 接口較少的情況下,設(shè)計(jì)了以16 位并行端口I2C擴(kuò)展器PCA9555a 為紐帶的矩陣鍵盤硬件連接和識別方法。多次實(shí)驗(yàn)結(jié)果表明,本文線反法優(yōu)化識別算法穩(wěn)定可靠、簡潔清晰、通用性好以及效率更高,擴(kuò)展法比較適用于GPIO接口資源稀少情況,具有較好的實(shí)用價值。