正在学习郭天祥《51　单片机　C语言教程(第2版)》第4章,键盘检测原理。矩阵键盘检测部分的代码又长又啰嗦,所以修改了下。　权当个记录。...矩阵　键盘扫描方法　有　两种　,书中采用的是　逐行扫描　。　在此尝试用　正在学习郭天祥《51单片机C语言教程(第2版)》第4章,键盘检测原理。矩阵键盘检测部分的代码又长又啰嗦,所以修改了下。　权当个记录。　问题描述:　在实验板TX-1C上实现如下描述,实验板上电时,数码管不显示,顺序按下矩阵键盘后,在数码管上依次显示0-F,6个数码管同时静态显示即可。　当前代码实现的是:按键按下后,数码管亮起;手放下,不按,数码管关闭不亮。　尤其说明,只有按键状态变化时,才会重新扫描,数码管的亮灭状态才会变化,而不是连续重复扫描,数码管持续高速亮灭变化。　矩阵键盘扫描方法有两种,书中采用的是　逐行扫描　。　在此尝试用　行列扫描:学习板上,行连接的是P3的低4位,列连接的是P3的高4位。所以,当P3口高4位输出高电平,低4位输出低电平,若这4行按键里有按键按下了,那么那一列对应的I/O电平就会拉低,我们就可知道按键的列坐标。类似地,高4位输出低电平,低4位输出高电平,可以得到按键的行坐标。　(上面一段话引用自文章《矩阵键盘的原理及应用》,链接:　link　.其中因为按键的接线不同,而有些说法不同。　)　代码如下:　#include　<　reg52　.　h　>　sbit　dula　=　P2　^　6　;　sbit　wela　=　P2　^　7　;　unsigned　char　code　table　[　]　=　{　0x3f　,　0x06　,　0x5b　,　0x4f　,　0x66　,　0x6d　,　0x7d　,　0x07　,　0x7f　,　0x6f　,　0x77　,　0x7c　,　0x39　,　0x5e　,　0x79　,　0x71　}　;　int　delayms　(　unsigned　int　)　;　int　LED_on　(　unsigned　int　P0we　,　unsigned　int　P0du　)　;　unsigned　int　matrixkeyscn　(　)　;　int　main　(　void　)　{　unsigned　int　num　,　num0　=　16　,　temp　;　while　(　1　)　{　num　=　matrixkeyscn　(　)　;　if　(　num　!=　num0　)　//　按键状态有变,则(才)需要操作　{　if　(　num　<　16　)　//　按了不同的按键　{　LED_on　(　0xc0　,　table　[　num　点亮对应的数码管　P3　=　0x0f　;　temp　=　P3　;　while　(　temp　!=　0x0f　)　//　等放开按键　{　temp　=　P3　;　}　}　else　//　先前按了,现在不按了　{　LED_on　(　0xc0　,　0x00　)　;　//　关闭数码管　P3　=　0x0f　;　temp　=　P3　;　while　(　temp　==　0x0f　)　//　等再按下按键　{　temp　=　P3　;　}　}　num0　=　num　;　}　}　return　0　;　}　unsigned　int　matrixkeyscn　(　)　//　键盘阵列,行列扫描　{　unsigned　char　temp　;　unsigned　int　hang　,　lie　,　key　=　16　;　P3　=　0x0f　;　temp　=　P3　;　if　(　temp　!=　0x0f　)　{　delayms　(　10　)　;　//　去抖延时　P3　=　0x0f　;　temp　=　P3　;　if　(　temp　!=　0x0f　)　{　P3　=　0x0f　;　temp　=　P3　;　switch　(　temp　)　{　case　0x0e　:　hang　=　1　;　break　;　case　0x0d　:　hang　=　2　;　break　;　case　0x0b　:　hang　=　3　;　break　;　case　0x07　:　hang　=　4　;　break　;　}　P3　=　0xf0　;　temp　=　P3　;　switch　(　temp　)　{　case　0xe0　:　lie　=　1　;　break　;　case　0xd0　:　lie　=　2　;　break　;　case　0xb0　:　lie　=　3　;　break　;　case　0x70　:　lie　=　4　;　break　;　}　key　=　(　hang　-　1　)　*　4　+　lie　-　1　;　}　}　return　key　;　}　int　LED_on　(　unsigned　int　P0we　,　unsigned　int　P0du　)　{　wela　=　1　;　P0　=　P0we　;　wela　=　0　;　P0　=　0xff　;　//　消影　dula　=　1　;　P0　=　P0du　;　dula　=　0　;　return　0　;　}　int　delayms　(　unsigned　int　xms　)　{　unsigned　int　i　,　j　;　for　(　i　=　0　;　i　<　xms　;　i　++　)　for　(　j　=　0　;　j　<　110　;　j　++　)　;　return　0　;　}　目录STM32操作矩阵　键盘　的　两种方法　——　扫描　和中断一、矩阵　键盘　的结构和原理二、　扫描　式矩阵　键盘　的原理和实现三、中断式矩阵　键盘　的原理和实现四、　两种　方案优劣　STM32操作矩阵　键盘　的　两种方法　——　扫描　和中断　最近在准备...　最近在准备电赛,有一道题要求设计一个简易的信号发生器,需要用到矩阵键盘来输入待生成信号的类型、频率和幅值等参数,【代码链接请戳这里】因此写下本文来总结我试验过的单片机操作矩阵键盘的两种方法。　一、矩阵键盘的结构和原理　矩阵键盘的实物可能长这样:　也可能长这样:　不管它们外表什么样,它们的内部结构都大同小异:　每一个矩阵键盘有8个信号端口,其中四个与行相连,剩下四个与列相连。当某一个按键按下时,对应行与列的端口就通过按键短接起来了。如何利用这个特性来让STM32判断是哪个按键按下呢?下面笔者总结了常见的两种方法。　二、扫描式矩阵键盘的原理和实现　首先明确一点,当STM32的GPIO配置成　开漏输出+上拉电阻　时,具有准双向IO的功能。　利用扫描的方式判断键盘按下的基本思想为:先把行全部置低,列全部置高,读取每行的电平,为高的即为按下的行;然后把列全部置低,行全部置高,读取每列的电平,为高的即为按下的列。代码如下:　//按键初始化函数　//PA0~3接1~4行,PA4~7接1~4列void　KEY_Init　(　void　)　{　GPIO_InitTypeDef　GPIO_InitStructure　;　RCC_AHB1PeriphClockCmd　(　RCC_AHB1Periph_GPIOA　,　ENABLE　)　;　//使能GPIOA时钟　GPIO_InitStructure　.　GPIO_Pin　=　GPIO_Pin_0　|　GPIO_Pin_1　|　GPIO_Pin_2　|　GPIO_Pin_3　|　GPIO_Pin_4　;　GPIO_InitStructure　.　GPIO_Pin　|　=　GPIO_Pin_4　|　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　;　GPIO_InitStructure　.　GPIO_Mode　=　GPIO_Mode_OUT　;　//普通输出模式　GPIO_InitStructure　.　GPIO_OType　=　GPIO_OType_OD　;　//开漏输出　GPIO_InitStructure　.　GPIO_Speed　=　GPIO_Speed_100MHz　;　//100M　GPIO_InitStructure　.　GPIO_PuPd　=　GPIO_PuPd_UP　;　//上拉　GPIO_Init　(　GPIOA　,　&　GPIO_InitStructure　)　;　//初始化　}　//按键处理函数　//返回按键值　/*mode　=1代表支持连按,mode　=　0代表不支持联连按*/　u8　matrixkey　(　int　mode　)　{　u8　row　=　0　;　u8　column　=　0　;　static　u8　key　=　1　;　GPIO_Write　(　GPIOA　,　0x0F　)　;　if　(　mode　)　key　=　1　;　if　(　key　&&　(　GPIO_ReadInputData　(　GPIOA　)　&　0xFF　)　!=　0x0F　)　{　//说明有按键按下　delay_ms　(　10　)　;　//按下消抖　column　=　GPIO_ReadInputData　(　GPIOA　)　&　0x0F　;　//列数　GPIO_Write　(　GPIOA　,　0xF0　)　;　delay_ms　(　1　)　;　row　=　GPIO_ReadInputData　(　GPIOA　)　&　0xF0　;　//行数　key　=　0　;　}　else　if　(　(　GPIO_ReadInputData　(　GPIOA　)　&　0XFF　)　==　0X0F　)　{　//说明无按键按下　delay_ms　(　10　)　;　//松手消抖　key　=　1　;　}　return　row　+　column　;　//如:返回11011011代表第三行第二列　}　//矩阵键盘选择所实现功能的函数　//入口参数:按下的键值　//出口参数:无void　matrixkey_function　(　u8　key　)　{　if　(　key　)　{　switch　(　key　)　{　//第一行　case　0xee　:　break　;　case　0xde　:　break　;　case　0xbe　:　break　;　case　0x7e　:　break　;　//第二行　case　0xed　:　break　;　case　0xbd　:　break　;　case　0xdd　:　break　;　case　0x7d　:　break　;　//第三行　case　0xeb　:　break　;　case　0xdb　:　break　;　case　0xbb　:　break　;　case　0x7b　:　break　;　//第四行　case　0xe7　:　break　;　case　0xd7　:　break　;　case　0xb7　:　break　;　case　0x77　:　break　;　}　}　}　三、中断式矩阵键盘的原理和实现　同样,首先明确GPIO的配置。中断方式下,应该将四行配置为　推挽输出　,四列配置为　输入+上拉电阻+下降沿中断　模式,当然,行列互换也是可以的。　中断式矩阵键盘的基本思想是,初始化时将四行拉低,当有按键按下时,这一列对应的端口会检测到下降沿,进而程序进入该列的中断服务函数,在中断服务函数中,将四行拉高,然后依次拉低每一行,同时检测该列的端口是否为低电平,如果为低电平,则证明是该行该列按下。代码如下:　//矩阵键盘端口初始化　//PF1~4接列　PF5~8接行　void　MatrixKEY_Init　(　void　)　{　GPIO_InitTypeDef　GPIO_InitStructure　;　RCC_AHB1PeriphClockCmd　(　RCC_AHB1Periph_GPIOF　,　ENABLE　)　;　//使能GPIOF时钟　GPIO_InitStructure　.　GPIO_Pin　=　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　;　GPIO_InitStructure　.　GPIO_Mode　=　GPIO_Mode_OUT　;　//通用输出模式　GPIO_InitStructure　.　GPIO_OType　=　GPIO_OType_PP　;　//推挽输出　GPIO_InitStructure　.　GPIO_Speed　=　GPIO_Speed_100MHz　;　//100M　GPIO_Init　(　GPIOF　,　&　GPIO_InitStructure　)　;　//初始化GPIOF5678　GPIO_ResetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//初始化输出置低　GPIO_InitStructure　.　GPIO_Pin　=　GPIO_Pin_1　|　GPIO_Pin_2　|　GPIO_Pin_3　|　GPIO_Pin_4　;　GPIO_InitStructure　.　GPIO_Mode　=　GPIO_Mode_IN　;　//普通输入模式　GPIO_InitStructure　.　GPIO_Speed　=　GPIO_Speed_100MHz　;　//100M　GPIO_InitStructure　.　GPIO_PuPd　=　GPIO_PuPd_UP　;　//上拉　GPIO_Init　(　GPIOF　,　&　GPIO_InitStructure　)　;　//初始化GPIOF1234　}　*　/　//第一行　void　EXTI1_IRQHandler　(　void　)　{　u8　i　;　delay_ms　(　50　)　;　if　(　PFin　(　1　)　==　0　)　{　/第一行　EXTIX1_4_disable　(　)　;　//关闭矩阵键盘的中断　for　(　i　=　5　;　i　<　9　;　i　++　)　{　GPIO_SetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为高电平　PFout　(　i　)　=　0　;　delay_ms　(　50　)　;　if　(　PFin　(　1　)　==　0　)　{　switch　(　i　)　{　case　(　5　)　:　num　=　1　;　break　;　case　(　6　)　:　num　=　2　;　break　;　case　(　7　)　:　num　=　3　;　break　;　case　(　8　)　:　num　=　12　;　break　;　default　:　num　=　16　;　}　}　}　GPIO_ResetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为低电平　EXTIX1_4_enable　(　)　;　}　EXTI_ClearITPendingBit　(　EXTI_Line1　)　;　//清除中断标志位*/　}　void　EXTI2_IRQHandler　(　void　)　{　u8　i　;　delay_ms　(　50　)　;　if　(　PFin　(　2　)　==　0　)　{　/第二行　EXTIX1_4_disable　(　)　;　//关闭矩阵键盘的中断　for　(　i　=　5　;　i　<　9　;　i　++　)　{　GPIO_SetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为高电平　PFout　(　i　)　=　0　;　delay_ms　(　50　)　;　if　(　PFin　(　2　)　==　0　)　{　switch　(　i　)　{　case　(　5　)　:　num　=　4　;　break　;　case　(　6　)　:　num　=　5　;　break　;　case　(　7　)　:　num　=　6　;　break　;　case　(　8　)　:　num　=　13　;　break　;　default　:　num　=　16　;　}　}　}　GPIO_ResetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为低电平　EXTIX1_4_enable　(　)　;　}　EXTI_ClearITPendingBit　(　EXTI_Line2　)　;　//清除中断标志位*/　}　void　EXTI3_IRQHandler　(　void　)　{　u8　i　;　delay_ms　(　50　)　;　if　(　PFin　(　3　)　==　0　)　{　第三行　EXTIX1_4_disable　(　)　;　//关闭矩阵键盘的中断　for　(　i　=　5　;　i　<　9　;　i　++　)　{　GPIO_SetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为高电平　PFout　(　i　)　=　0　;　delay_ms　(　50　)　;　if　(　PFin　(　3　)　==　0　)　{　switch　(　i　)　{　case　(　5　)　:　num　=　7　;　break　;　case　(　6　)　:　num　=　8　;　break　;　case　(　7　)　:　num　=　9　;　break　;　case　(　8　)　:　num　=　14　;　break　;　default　:　num　=　16　;　}　}　}　GPIO_ResetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为低电平　EXTIX1_4_enable　(　)　;　}　EXTI_ClearITPendingBit　(　EXTI_Line3　)　;　//清除中断标志位*/　}　void　EXTI4_IRQHandler　(　void　)　{　u8　i　;　delay_ms　(　50　)　;　if　(　PFin　(　4　)　==　0　)　{　第四列　EXTIX1_4_disable　(　)　;　//关闭矩阵键盘的中断　for　(　i　=　5　;　i　<　9　;　i　++　)　{　GPIO_SetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为高电平　PFout　(　i　)　=　0　;　delay_ms　(　50　)　;　if　(　PFin　(　4　)　==　0　)　{　switch　(　i　)　{　case　(　5　)　:　num　=　10　;　break　;　case　(　6　)　:　num　=　0　;　break　;　case　(　7　)　:　num　=　11　;　break　;　case　(　8　)　:　num　=　15　;　break　;　default　:　num　=　16　;　}　}　}　GPIO_ResetBits　(　GPIOF　,　GPIO_Pin_5　|　GPIO_Pin_6　|　GPIO_Pin_7　|　GPIO_Pin_8　)　;　//置为低电平　EXTIX1_4_enable　(　)　;　}　EXTI_ClearITPendingBit　(　EXTI_Line4　)　;　//清除中断标志位*/　}　//中断优先级配置　void　NVICx_Init　(　void　)　{　NVIC_InitTypeDef　NVIC_InitStructure　;　NVIC_PriorityGroupConfig　(　NVIC_PriorityGroup_2　)　;　//设置系统中断优先级分组2　NVIC_InitStructure　.　NVIC_IRQChannel　=　EXTI1_IRQn　;　//外部中断1　NVIC_InitStructure　.　NVIC_IRQChannelPreemptionPriority　=　0　;　//抢占优先级2　NVIC_InitStructure　.　NVIC_IRQChannelSubPriority　=　0　;　//子优先级2　NVIC_InitStructure　.　NVIC_IRQChannelCmd　=　ENABLE　;　//中断通道使能　NVIC_Init　(　&　NVIC_InitStructure　)　;　NVIC_InitStructure　.　NVIC_IRQChannel　=　EXTI2_IRQn　;　//外部中断2　NVIC_InitStructure　.　NVIC_IRQChannelPreemptionPriority　=　0　;　//抢占优先级2　NVIC_InitStructure　.　NVIC_IRQChannelSubPriority　=　1　;　//子优先级2　NVIC_InitStructure　.　NVIC_IRQChannelCmd　=　ENABLE　;　//中断通道使能　NVIC_Init　(　&　NVIC_InitStructure　)　;　NVIC_InitStructure　.　NVIC_IRQChannel　=　EXTI3_IRQn　;　//外部中断3　NVIC_InitStructure　.　NVIC_IRQChannelPreemptionPriority　=　0　;　//抢占优先级2　NVIC_InitStructure　.　NVIC_IRQChannelSubPriority　=　2　;　//子优先级2　NVIC_InitStructure　.　NVIC_IRQChannelCmd　=　ENABLE　;　//中断通道使能　NVIC_Init　(　&　NVIC_InitStructure　)　;　NVIC_InitStructure　.　NVIC_IRQChannel　=　EXTI4_IRQn　;　//外部中断4　NVIC_InitStructure　.　NVIC_IRQChannelPreemptionPriority　=　0　;　//抢占优先级2　NVIC_InitStructure　.　NVIC_IRQChannelSubPriority　=　3　;　//子优先级2　NVIC_InitStructure　.　NVIC_IRQChannelCmd　=　ENABLE　;　//中断通道使能　NVIC_Init　(　&　NVIC_InitStructure　)　;　}　//外部中断初始化程序　//初始化PF1~4为中断输入.　void　EXTIX_Init　(　void　)　{　EXTI_InitTypeDef　EXTI_InitStructure　;　NVICx_Init　(　)　;　MatrixKEY_Init　(　)　;　//按键对应的IO口初始化　RCC_APB2PeriphClockCmd　(　RCC_APB2Periph_SYSCFG　,　ENABLE　)　;　//使能SYSCFG时钟　SYSCFG_EXTILineConfig　(　EXTI_PortSourceGPIOF　,　EXTI_PinSource1　)　;　//PF1　连接到中断线1　SYSCFG_EXTILineConfig　(　EXTI_PortSourceGPIOF　,　EXTI_PinSource2　)　;　//PF2　连接到中断线2　SYSCFG_EXTILineConfig　(　EXTI_PortSourceGPIOF　,　EXTI_PinSource3　)　;　//PF3　连接到中断线3　SYSCFG_EXTILineConfig　(　EXTI_PortSourceGPIOF　,　EXTI_PinSource4　)　;　//PF4　连接到中断线4　/*　配置EXTI_Line1,2,3,4　*/　EXTI_InitStructure　.　EXTI_Line　=　EXTI_Line1　|　EXTI_Line2　|　EXTI_Line3　|　EXTI_Line4　;　EXTI_InitStructure　.　EXTI_Mode　=　EXTI_Mode_Interrupt　;　//中断事件　EXTI_InitStructure　.　EXTI_Trigger　=　EXTI_Trigger_Falling　;　//下降沿触发　EXTI_InitStructure　.　EXTI_LineCmd　=　ENABLE　;　//中断线使能　EXTI_Init　(　&　EXTI_InitStructure　)　;　//配置　}　//关闭外部中断pF1~4　void　EXTIX1_4_disable　(　void　)　{　EXTI_InitTypeDef　EXTI_InitStructure　;　/*　配置EXTI_Line1,2,3,4　*/　EXTI_InitStructure　.　EXTI_Line　=　EXTI_Line1　|　EXTI_Line2　|　EXTI_Line3　|　EXTI_Line4　;　EXTI_InitStructure　.　EXTI_Mode　=　EXTI_Mode_Interrupt　;　//中断事件　EXTI_InitStructure　.　EXTI_Trigger　=　EXTI_Trigger_Falling　;　//下降沿触发　EXTI_InitStructure　.　EXTI_LineCmd　=　DISABLE　;　//中断线失能　EXTI_Init　(　&　EXTI_InitStructure　)　;　//配置　}　//开启外部中断pb1~4　void　EXTIX1_4_enable　(　void　)　{　EXTI_InitTypeDef　EXTI_InitStructure　;　/*　配置EXTI_Line1,2,3,4　*/　EXTI_InitStructure　.　EXTI_Line　=　EXTI_Line1　|　EXTI_Line2　|　EXTI_Line3　|　EXTI_Line4　;　EXTI_InitStructure　.　EXTI_Mode　=　EXTI_Mode_Interrupt　;　//中断事件　EXTI_InitStructure　.　EXTI_Trigger　=　EXTI_Trigger_Falling　;　//下降沿触发　EXTI_InitStructure　.　EXTI_LineCmd　=　ENABLE　;　//中断线使能　EXTI_Init　(　&　EXTI_InitStructure　)　;　//配置　}　四、两种方案优劣　扫描法占用资源少,但是当工程复杂度增高时,按键会变得很不“灵敏”;　中断法占用资源相对较多(四根中断线),但是能在工程复杂度增高的情况下,很好地保持按键的灵敏度。　一般地,　键盘　分为编码和非编码　两种　。编码方式的　键盘　要使用专门的硬件来识别按键,系统比较复杂且占用较多的硬件资源。实际应用中编码方式的　键盘　使用得越来越少,相应地大多数系统都采用非编码键,并用软件的方式对...　一、本文内容提要　...从各发光二极管的电极连接方式又可以分为共阳极和共阴极　两种　类型。共阴极则是所有的二极管的阴极连接在一起,而阳极是分离的(见图1(b));而共阳极就是所有二极管的阳极是公共相连,　一、本文内容提要　...从各发光二极管的电极连接方式又可以分为共阳极和共阴极　两种　类型。共阴极则是所有的二极管的阴极连接在一起,而阳极是分离的(见图1(b));而共阳极就是所有二极管的阳极是公共相连,　在本文中,小编将对pic单片机的4×4行列式键盘的工作原理加以介绍,以帮助大家增进对pic　单片机键盘　系统的了解,方便后期编程使用。(1)单片机系统键盘原理行列式键盘的接法比独立式键盘的接法复杂,编程实现上也会...　按键可以说是51　单片机　项目开发的一个重要组件了,它是作为51　单片机　IO口...矩阵　键盘　是通过　单片机扫描键盘　的每行每列,通过行和列唯一确定按键的编号,下面对这　两种方法　进行介绍。　1　独立按键　Proteus仿真电路图　途中　按键可以说是51单片机项目开发的一个重要组件了,它是作为51单片机IO口输入的一种重要方式。我们可以通过按键控制单片机执行相应的程序,得到我们想要得效果。51单片机的按键输入主要有两种,一种是独立按键,每个按键对应一个IO口,通过循环或者中断去检测,这种方法常用于按键个数较少得时候。如果按键的个数较多,每个按键都分配一个IO口,会占用大量的资源,所以就有了矩阵键盘。矩阵键盘是通过单片机扫描键盘的每行每列,通过行和列唯一确定按键的编号,下面对这两种方法进行介绍。　1　独立按键　Proteus仿真电路图　途中按键通过P1.0　P1.3输入,四个LED灯连接P.0　P2.3,不同按键按下,对应不同的LED灯亮,程序如下:　#include　unsigned　char　num;void　main(){　TMOD=0x01;　TH0　=　(65536-917)/256;　//1000/1.09　TL0　=　(65536-917)%256;;　EA=1;　ET0=1;　TR0=1;　P1=0xff;　P2=0x00;　while(1){　if(num==20){　num=0;　switch(P1){　case　~0x01:　P2=~0x01;　break;　case　~0x02:　P2=~0x02;　break;　case　~0x04:　P2=~0x04;　break;　case　~0x08:　P2=~0x08;　break;　defult:　break;　}　}}　}void　Timer0()　interrupt　1{　TH0　=　(65536-917)/256;　TL0　=　(65536-917)%256;　num++;}　采用的是定时器0中断的形式,定时器0每1ms产生一次中断,num加一,再while循环中判断num=20时候,也就是计时20ms,读取P1的状态,通过判断P1的值判断是那个按键按下,点亮对应的LED灯。效果图如下:　独立按键很简单,想必大家有比我这更好的例子(比如加上消抖),今天的重点时矩阵键盘。　2　矩阵键盘检测　Proteus仿真电路图　这里将16个按键按照4*4排列,矩阵键盘的每一行分别连接P3.0　P3.3;矩阵键盘的每一列连接P3.4　P3.7。扫描的原理大致是这样的:先将P30置零,其余都为1(P3-0xfe),然后读取P3口的状态,如果P3!=0xfe了(这里主要是高四位会变化,第四位不变),说明有按键按下了,而且一定是第一行中的某个按键。然后就去判断P3口的状态,如果第一个按键按下(左上角),那应该是P3.4=0,刚才P3.0=0,那么P3口的状态就是P3=0xee,以此类推,第一行第二个,第三个,第四个按键按下,对应P3=0xde,　P3=0xbe,　P3=0x7e。这样就可以判断是哪个按键按下了,如果P3=0xfe,就说明第一行没有按键按下,那么同样的方法,令P3=0xfd,判断第二行是否有按键按下,依次给每一行置零(P3=0xfb,P3=0xf7),读取列的状态,判断是哪个按键按下,如果都没有,说明没有按键按下。　C51代码:　#include　void　Delay10ms()　//@11.0592MHz{　unsigned　char　i,　j;　i　=　18;　j　=　235;　do　{　while　(--j);　}　while　(--i);}void　key_scan(){　unsigned　char　temp;　P3=0xfe;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xee:　P2=0x00;　break;　case　0xde:　P2=0x01;　break;　case　0xbe:　P2=0x02;　break;　case　0x7e:　P2=0x03;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xfd;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xed:　P2=0x04;　break;　case　0xdd:　P2=0x05;　break;　case　0xbd:　P2=0x06;　break;　case　0x7d:　P2=0x07;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xfb;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xeb:　P2=0x08;　break;　case　0xdb:　P2=0x09;　break;　case　0xbb:　P2=0x0a;　break;　case　0x7b:　P2=0x0b;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xf7;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xe7:　P2=0x0c;　break;　case　0xd7:　P2=0x0d;　break;　case　0xb7:　P2=0x0e;　break;　case　0x77:　P2=0x0f;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;void　main(){　P2=0x00;　while(1)　{　key_scan();　}}　Key_scan()是键盘扫描函数,主要分为四大部分,每个部分核心都是一样的,就是实现上述矩阵键盘扫描原理,下面拿出第一段介绍:　P3=0xfe;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xee:　P2=0x00;　break;　case　0xde:　P2=0x01;　break;　case　0xbe:　P2=0x02;　break;　case　0x7e:　P2=0x03;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　首先P3=0xfe,再将P3赋值给temp,temp再与0xf0与运算,如果P3的高四位有0出现,那么temp一定不等于0xf0,但是这样还不能直接判断就是有按键按下了,可能是一些干扰,而且按键按下时候初期时不稳定的,不是一个理想的下降沿,所以需要消抖,其实就是延时10ms再次读取P3口状态,如果现在tmep依然不等于0xf0,那么可以判断有按键按下了,然后就可以吧p3幅值给temp,进而判断temp的状态了。　然后下面这段程序就是判断按键是否抬起,否则的话会一直在这个循环,无法执行其他程序:　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　上述代码时通过while循环实现的,也可以中断实现:　#include　unsigned　char　num;void　Delay10ms()　//@11.0592MHz{　unsigned　char　i,　j;　i　=　18;　j　=　235;　do　{　while　(--j);　}　while　(--i);}void　key_scan(){　unsigned　char　temp;　P3=0xfe;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xee:　P2=0x00;　break;　case　0xde:　P2=0x01;　break;　case　0xbe:　P2=0x02;　break;　case　0x7e:　P2=0x03;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xfd;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xed:　P2=0x04;　break;　case　0xdd:　P2=0x05;　break;　case　0xbd:　P2=0x06;　break;　case　0x7d:　P2=0x07;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xfb;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xeb:　P2=0x08;　break;　case　0xdb:　P2=0x09;　break;　case　0xbb:　P2=0x0a;　break;　case　0x7b:　P2=0x0b;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;　}　}　}　P3=0xf7;　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　delay10ms();　temp=P3;　temp&=0xf0;　if(temp!=0xf0)　{　temp=P3;　switch(temp)　{　case　0xe7:　P2=0x0c;　break;　case　0xd7:　P2=0x0d;　break;　case　0xb7:　P2=0x0e;　break;　case　0x77:　P2=0x0f;　break;　}　while(temp!=0xf0)　{　temp=P3;　temp&=0xf0;void　main(){　TMOD=0x01;　TH0　=　(65536-917)/256;　//1000/1.09　TL0　=　(65536-917)%256;;　EA=1;　ET0=1;　TR0=1;　P2=0x00;　while(1)　{　if(num==2)　{　num=0;　key_scan();　}　}}void　Timer0()　interrupt　1{　TH0　=　(65536-917)/256;　TL0　=　(65536-917)%256;　num++;}　核心的内容都一样,就不多解　前言　...  矩阵　键盘　常用的有以下　两种扫描　方式,相比较之下,小飞哥更推荐第二种　扫描　方式,第二种　扫描　方式能够更有效地避免错误按键,本次分享的也是第二种　扫描　方式的代码。　逐行　扫描　:高四位输　前言　  日常设计中,不可避免的会使用到按键,像我们常见的　POS　机、计算器等设备用到的按键是非常多的,如果采用普通的　1　个IO　1　个按键的设计方法,显然对单片机资源来说是非常浪费的,所以采用类似矩阵的设计思路,能够大大减少　MCU　IO　的使用,也是我们所说的矩阵键盘。　应用场景　一、矩阵键盘的两种扫描方式　  矩阵键盘常用的有以下两种扫描方式,相比较之下,小飞哥更推荐第二种扫描方式,第二种扫描方式能够更有效地避免错误按键,本次分享的也是第二种扫描方式的代码。　逐行扫描:高四位输出低电平来对矩阵键盘逐行扫描,当低四位接受数据不全为一,表示有按键按下,然后通过接收到的数据是哪一位为　0　来判断哪个按键被按下,可以简单理解为,“一厢情愿型”;　行列扫描:高四位全部输出低电平,低四位输出高电平。当接受到的数据,低四位不全为高电平时,说明有按键按下。然后通过接收到的值判断是哪一列有按键按下,然后再反过来高四位输出高电平,第四位低电平,然后通过高四位接收到的值判断哪一行按键按下,可以简单理解为,“两情相悦型”;　二、“两情相悦型”按键扫描方式详解　  上面图片是　4*4　矩阵键盘电路设计,ROW1-ROW4　为键盘你的行,COL1-COL4　为键盘的列,8　个IO　共16　个按键,我们以　K1　按下为例,分析具体的工作流程:　1、　先把行切换为上拉输入模式　2、把列切换为输出模式,输出低电平　3、获取当前行输入状态　即:ROW1　线上此时为低电平,按下之前　ROW1-ROW4　为:1111,即0x0F,按下之后,ROW1　与COL1　连通,ROW1-ROW4　的值变为:1110,即0x0e,此时获取到按键所在行;　4、把行切换为输出状态,输出低电平　5、把列切换为上拉输入模式　6、获取当前列输入状态　即:COL1　线上此时为低电平,按下之前　COL1-COL4　为:1111,即0x0F,按下之后,ROW1　与COL1　连通,COL1-COL4　的值变为:1110,即0x0e,此时获取到按键所在列;　7、两次行列状态获取完成之后,即确定了按键所在行列,得到按键编码:0xee,值并不是唯一的　0xee,具体跟硬件连接有关系。　硬件连接　  调试用的键盘为这种非常简单的,某宝几毛钱,邮费都不够的说~　MCU　键盘　PF0　ROW1　PF1　ROW2　PF2　ROW3　PF3　ROW4　PG0　COL1　PG1　COL2　PG2　COL3　PG3　COL4　软件实现　1、cubemx配置键盘IO　  行IO配置为输出模式,输出低电平　  列IO配置为输入模式,需要注意的是,配置为输入上拉模式　  配置完成的IO状态为:　  时钟配置、串口等配置见:HAL库使用章节　2、代码编写　  根据上一小节描述,按键按下之后是对应一个编码,根据上面的描述,我们可以计算出一个码表,也即按键真值表:　const uint8_t KeyTrueLable[] = { 0xEE, 0xDE, 0xBE, 0x7E, 0xED, 0xDD, 0xBD, 0x7D, 0xEB, 0xDB, 0xBB, 0x7B, 0xE7, 0xD7, 0xB7, 0x77};　  真值表是硬件IO状态的一种表达,对用户来说并不是需要的,需要攻城狮转换为用户需要的信息,也即是,建立一个与之对应的用户信息表:　const uint8_t KeyUserDefined[] = { 1, 2, 3, 12, 4, 5, 6, 13, 7, 8, 9, 14, 10, 0, 11, 15};　  根据上面原理介绍,我们需要封装4个函数,行输入、行输出、列输入、列输出:行输入/void KeyPad_SetROW_IN(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOF_CLK_ENABLE(); //ROW0-ROW3设置成上拉输入 GPIO_InitStruct.Pin = Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin; //KEY0-KEY3 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct)行输出/void KeyPad_SetROW_Out(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOF_CLK_ENABLE(); /*Configure GPIO pins : PFPin PFPin PFPin PFPin */ GPIO_InitStruct.Pin = Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOF, &GPIO_InitStruct); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOF, Key_ROW1_Pin | Key_ROW2_Pin | Key_ROW3_Pin | Key_ROW4_Pin, GPIO_PIN_RESET)列输入/void KeyPad_SetCol_IN(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOG_CLK_ENABLE(); //C0-3设置成上拉输入 /*Configure GPIO pins : PCPin PCPin PCPin PCPin */ GPIO_InitStruct.Pin = Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct)列输出/void KeyPad_SetCol_Out(void){ GPIO_InitTypeDef GPIO_InitStruct = {0}; __HAL_RCC_GPIOC_CLK_ENABLE(); GPIO_InitStruct.Pin = Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin; //KEY0-KEY3 GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOG, &GPIO_InitStruct); HAL_GPIO_WritePin(GPIOG, Key_COL1_Pin | Key_COL2_Pin | Key_COL3_Pin | Key_COL4_Pin, GPIO_PIN_RESET);}　然后编写我们的按键真值获取函数:按键扫描程序//先C低读R状态,再R低读C状态,判断是那个按键按下/uint8_t KeyPad_KEY_Scan(void){ uint8_t keycode, keycode_temp; //C低读ROW KeyPad_SetCol_Out(); keycode = KEYPADROW & 0x0f; //R低读COL KeyPad_SetROW_Out(); KeyPad_SetCol_IN(); keycode_temp = KEYPADCOL; keycode |= (keycode_temp << 4 & 0xf0); KeyPad_SetROW_IN(); return keycode;}　前面说过,直接获取到的是按键IO的状态组合,需要转换为实际需要的信息。封装一下函数:键值转换用户定义键值/uint8_t KeyPad_KeyTrueLableGet(uint8_t key){ uint8_t i = 0, R_key_user = 0xE0; for (i = 0; i < D_Key_Count; i++) { if (key == KeyTrueLable[i]) { R_key_user = KeyUserDefined[i]; } } return R_key_user;得到键值/uint8_t KeyPad_GetKeyValue(void){ uint8_t KeyValue = 0; // HAL_Delay(10); if (KeyPad_KEY_Scan() == D_NO_KEY_CODE) //没有按键/按键抬起 { if (keypad_status.KeyPress == 1) { keypad_status.KeyValue_New = 0; keypad_status.KeyValue_Old = 0; keypad_status.KeyPress = 0; } return 0xF0; } HAL_Delay(10); KeyValue = KeyPad_KEY_Scan(); if (KeyValue == D_NO_KEY_CODE) { if (keypad_status.KeyPress == 1)解决按键“0” 的问题 keypad_status.KeyValue_New = 0xF2; keypad_status.KeyValue_Old = 0xF2; keypad_status.KeyPress = 0; } return 0xF0; } KeyValue = KeyPad_KeyTrueLableGet(KeyValue); keypad_status.KeyPress = 1; //判断是否是第一次按下 keypad_status.KeyValue_New = KeyValue; if (keypad_status.KeyValue_New == keypad_status.KeyValue_Old) { return 0xF1; } keypad_status.KeyValue_Old = KeyValue; printf("\r\nkeycode is:%0x\r\n", KeyValue); return KeyValue;}　在keypad.h中定义:　#ifndef __KEYPAD_H#define __KEYPAD_H#include "main.h"#include "stm32f4xx.h"#define KEYPADROW GPIOF->IDR#define KEYPADCOL GPIOG->IDR#define D_Key_Count 16#define D_ERR_KEY_CODE 0xFF#define D_NO_KEY_CODE 0xFF/typedef struct keypad{ uint8_t KeyStatus; uint8_t KeyValue_New; uint8_t KeyValue_Old; uint8_t KeyPress; }Keypad_para;uint8_tKeyPad_KEY_Scan(void);uint8_t KeyPad_GetKeyValue(void);