笔记本矩阵键盘概述
在EC中支持两种形式的键盘,一种是PS/2接口的键盘,这种键盘几乎已经被USB接口的键盘所取代,所以PS/2接口的键盘不在我们的讨论范围。 另一种键盘则是矩阵键盘,也是笔记本电脑的内置键盘,该键盘类似于学习单片机时使用的4*4的那种键盘,笔记本使用的矩阵键盘有三种规格,分别是16*8、17*8和18*8。16、17和18代表着该款键盘的列数,8代表着键盘的行数,我们这里只讨论最简单的16*8。 笔记本的内置键盘的实物图如下图所示: 矩阵键盘对应的码值表,这个由键盘卖家提供,如下图所示: 矩阵键盘背面的排线,这个排线直接与EC芯片的矩阵键盘接口连接,如下两张图片所示:
矩阵键盘的扫描方式
行列扫描方式
这种方式适合于输入输出方向可以变化的场景,在EC中的矩阵键盘行为输入,列为输出且无法改变方向,这里不适合这种方式,在这里也不过多讨论。
逐行逐列扫描方式
这种方式则适合于行列数据线方向无法改变的情况,这里也以8行16列的键盘为例进行讨论,在EC中将所有的8行连接到了EC的KSI[7:0],EC接收着8根数据线的数据,将所有的16列连接到EC的KSO[15:0],EC通过该16根数据线输出数据。 在代码中是通过一个for循环来实现的,循环次数为列数,这里为16次。
- 通过KSO对第0列输出低电平,其他为高,即KSO[15:0]=0xFFFE,0b1111111111111110。
- 读取KSI[7:0],
如果KSI[7:0]!=0xFF,第1步中的输出低电平的列有按键按下,然后再通过KSI[7:0]的第几位为低电平确定按键按下的行号; 如果KSI[7:0]==0xFF,则代表没有按键按下,对第1步的列号加1,继续执行1-2步。 - 如果最后获取到了键码值,就是KSI[7:0]!=0xFF,就设置F_Service_SEND = 1,这样就可以通过KBC往主机发送按键值了,当然这里需要把行列位置转换为相应按键的Keyboard Scan Codes: Set 2对应的值。
EC中矩阵键盘是如何扫描并得到码值的(代码分析)
在主函数的服务函数中存在Service_KEYSCAN矩阵键盘键盘码扫描服务函数,该函数通过轮询的方式执行,会一直监视着矩阵键盘是否由相关动作产生,入口函数在CORE_MAIN.C函数中,如下图所示: Service_KEYSCAN矩阵键盘扫描服务函数在CORE_SCAN.C中,我们关注scan_key函数,该函数完成了矩阵键盘按键位置的获取,如下图所示: 分析BYTE scan_keys(void)中的这个for循环,键盘的行列位置扫描就是在这个for循环中完成的,使用的方法为前面内容中介绍的逐行逐列扫描方式,其中循环次数SCAN_Cnt为矩阵键盘的列数,Write_Strobe函数的作用是将当前操作的列数SCAN_Cnt电平拉低,其他15列的信号线为高,函数内容在下面有列出,
for (SCAN_Cnt=0;SCAN_Cnt<16;SCAN_Cnt++)
{
Write_Strobe(SCAN_Cnt);
#if ( _KBSCAN_DELAY > 0 )
Microsecond_Delay(_KBSCAN_DELAY);
#endif
SCAN_Val = KSI; // Read in KI 0 - 7 sense line data. */
SCAN_Val = (~SCAN_Val) ^ bscan_matrix[SCAN_Cnt];
if (SCAN_Val != 0)
{
check_scan(SCAN_Val, SCAN_Cnt);
}
if (bscan_matrix[SCAN_Cnt])
{ // Here, if current still active.
scan.saf_keys = 1; // Set keys active bits. Check all key release.
}
KSOL = 0xFF;
KSOH1 = 0xFF;
KSOH2 = 0xFF;
#if (ExternMatrixGPO)
GPOKeyPin1_HI();
#endif
}
void Write_Strobe(BYTE scan_line_num)
{
if (scan_line_num < 8)
{
KSOL =~ (0x01 << scan_line_num);
KSOH1 = 0xFF;
KSOH2 = 0xFF;
#if (ExternMatrixGPO)
GPOKeyPin1_HI();
#endif
}
else
{
KSOL = 0xFF;
KSOH1 =~ (0x01<<(scan_line_num-0x08));
KSOH2 = 0xFF;
#if (ExternMatrixGPO)
GPOKeyPin1_HI();
#endif
}
}
void Service_SEND(void)
{
//-------------------------------------------------------------------------
BYTE nKBData;
FLAG bBreak;
BYTE KBCmdAck;
//-------------------------------------------------------------------------
KeyboardSendTimer++;
TH1 = (T_Timer_SEND)>>8;
TL1 = (T_Timer_SEND); // Reload timer
TF1 = 0;
TR1 = 1;
ET1 = 1;
Timer_B.fbit.SEND_ENABLE = 1;
//-------------------------------------------------------------------------
if( IsFlag1(KBHISR,OBF) || IsFlag1(KBHISR,IBF) )
{
return;
}
if ( KBPendingRXCount > 0 )
{
KBCmdAck = GetKB_PendingData();
KBC_DataToHost(KBCmdAck);
return;
}
/* ------------------------------------------------------------------------
* Keyboard Buffer Data Send to Host(System)
* ---------------------------------------------------------------------- */
//if( Int_Var.Scan_Lock ) return;
if( Ccb42_DISAB_KEY ) return;
nKBData = Get_Buffer();
FORCE_NO_XLATE = 0;
if ( nKBData == 0xFF ) //0xFF: No key data in buffer
{
Timer_B.fbit.SEND_ENABLE = 0;
return;
}
else
{
bBreak = Gen_Info_BREAK_SCAN;
Gen_Info_BREAK_SCAN = 0;
#if ENABLE_S5_KEYBOARD_INTR
if( CORE_PMFLAG_S5 ) return;
#endif
#if ENABLE_S3_KEYBOARD_INTR
if( CORE_PMFLAG_S3 ) return;
#endif
if ( send_to_pc(nKBData, bBreak) )
{
Gen_Info_BREAK_SCAN = 1; // Break prefix code.
}
}
//-------------------------------------------------------------------------
}
首先通过Get_Buffer函数获取到刚刚按键的行列位置,再通过send_to_pc函数发送位置,进入send_to_pc函数:
static BYTE send_to_pc( BYTE data_word, BYTE break_prefix_flag )
{
return( common_send_to_pc( data_word, break_prefix_flag ) );
}
static BYTE common_send_to_pc( BYTE data_word, BYTE break_prefix_flag )
{
BYTE send_it = FALSE;
if ( Ccb42_XLATE_PC == 0 ) // Send data as is.
{
send_it = TRUE;
break_prefix_flag = FALSE;
}
else // Translation mode is enabled.
{
data_word = translate_to_pc( data_word, break_prefix_flag );
if ( data_word == 0xFF )
{
break_prefix_flag = TRUE; // Don't send break code prefix.
}
else if( data_word == 0x00 )
{
break_prefix_flag = TRUE;; // Don't send break code prefix.
}
else
{
break_prefix_flag = FALSE;
send_it = TRUE;
}
}
if ( send_it ) { Data_To_Host(data_word); }
return( break_prefix_flag );
}
里面会通过translate_to_pc函数将按键行列号转换为Set2中的按键键码值,
static BYTE translate_to_pc( BYTE data_word, BYTE break_prefix_flag )
{
/* Scan code set 2 to scan code set 1 translation table. First byte is a
dummy entry because scan code "0" is not translated. */
static const BYTE code scan2_table[] =
{
0x00, 0x43, 0x41, 0x3F, 0x3D, 0x3B, 0x3C, 0x58,
0x64, 0x44, 0x42, 0x40, 0x3E, 0x0F, 0x29, 0x59,
0x65, 0x38, 0x2A, 0x70, 0x1D, 0x10, 0x02, 0x5A,
0x66, 0x71, 0x2C, 0x1F, 0x1E, 0x11, 0x03, 0x5B,
0x67, 0x2E, 0x2D, 0x20, 0x12, 0x05, 0x04, 0x5C,
0x68, 0x39, 0x2F, 0x21, 0x14, 0x13, 0x06, 0x5D,
0x69, 0x31, 0x30, 0x23, 0x22, 0x15, 0x07, 0x5E,
0x6A, 0x72, 0x32, 0x24, 0x16, 0x08, 0x09, 0x5F,
0x6B, 0x33, 0x25, 0x17, 0x18, 0x0B, 0x0A, 0x60,
0x6C, 0x34, 0x35, 0x26, 0x27, 0x19, 0x0C, 0x61,
0x6D, 0x73, 0x28, 0x74, 0x1A, 0x0D, 0x62, 0x6E,
0x3A, 0x36, 0x1C, 0x1B, 0x75, 0x2B, 0x63, 0x76,
0x55, 0x56, 0x77, 0x78, 0x79, 0x7A, 0x0E, 0x7B,
0x7C, 0x4F, 0x7D, 0x4B, 0x47, 0x7E, 0x7F, 0x6F,
0x52, 0x53, 0x50, 0x4C, 0x4D, 0x48, 0x01, 0x45,
0x57, 0x4E, 0x51, 0x4A, 0x37, 0x49, 0x46, 0x54
};
BYTE check_break_bit = FALSE;
if ( data_word == 0xF0 )
{ /* Signify that break code prefix was encountered. */
data_word = 0xFF;
}
else if ( data_word == 0x00 )
{
data_word = 0x00; /* Key detection error/overrun. */
}
else if ( (data_word & 0x80) == 0 )
{ /* Translate codes 01 thru 7F. */
/* The variable "data" has scan code (set 2) to translate.
Set "data" to the translated (to set 1) scan code. */
data_word = scan2_table[data_word];
check_break_bit = TRUE;
}
else if ( data_word == 0x83 ) /* ID code for 101/102 keys. */
{
data_word = 0x41; /* Translate ID code. */
check_break_bit = TRUE;
}
else if ( data_word == 0x84 ) /* ID code for 84 keys. */
{
data_word = 0x54; /* Translate ID code. */
check_break_bit = TRUE;
}
if ( check_break_bit && break_prefix_flag )
{ /* Last code received by this routine was the break prefix.This must be
a break code. Set high bit to indicate that this is a break code. */
data_word |= 0x80;
}
return(data_word);
}
最后通过Data_To_Host函数将键码值发送给操作系统或者BIOS:
void Data_To_Host(BYTE data_byte)
{
#if OEM_HOOK_KB_DATA
if( OemHandle_Data_To_Host(data_byte) )
{
return;
}
#endif
SET_BIT( KBHISR,4 ); //Keyboard Enabled
CLEAR_BIT( KBHISR,5 ); //Transmit timeout
if ( Ccb42_INTR_KEY )
{
SET_BIT( KBHICR, 0 );
}
else
{
CLEAR_BIT( KBHICR, 0 );
}
KBHIKDOR = data_byte;
#if En_RecordKB60
RamDebug(data_byte);
#endif
}
|