矩阵键盘的使用
矩阵键盘的工作方式
对键盘的响应取决于键盘的工作方式,键盘的工作方式应根据实际应用系统中的CPU的工作状况而定,其选取的原则是既要保证CPU能及时响应按键操作,又不要过多占用CPU的工作时间。通常键盘的工作方式有三种,编程扫描、定时扫描和中断扫描。
(1)编程扫描方式
编程扫描方式是利用CPU完成其它工作的空余时间,调用键盘扫描子程序来响应键盘输入的要求。在执行键功能程序时,CPU不再响应键输入要求,直到CPU重新扫描键盘为止。
(2)定时扫描方式
定时扫描方式就是每隔一段时间对键盘扫描一次,它利用单片机内部的定时器产生一定时间(例如10ms)的定时,当定时时间到就产生定时器溢出中断。CPU响应中断后对键盘进行扫描,并在有按键按下时识别出该键,再执行该键的功能程序。
(3)中断扫描方式
上述两种键盘扫描方式,无论是否按键,CPU都要定时扫描键盘,而单片机应用系统工作时,并非经常需要键盘输入,因此,CPU经常处于空扫描状态。
为提高CPU工作效率,可采用中断扫描工作方式。其工作过程如下:当无按键按下时,CPU处理自己的工作,当有按键按下时,产生中断请求,CPU转去执行键盘扫描子程序,并识别键号。
参考CSDN博主「kaiclife」的原创文章https://blog.csdn.net/weixin_35381077/article/details/112668883
主体部分
键盘key_handler()函数矩阵键盘的主体部分,要使用键盘就得将这个key_handler函数放到主函数的while(1)前面。
void key_handler(void) //键盘程序的主要部分
{
//led (LED0,LED_ON);
sigh_refresh=1; //刷新显示屏的标志位
systick_delay_ms(STM0,500); //等待cpu0外设初始化
//全键盘扫描//
while(1) //进入主循环
{
i=0; //复位i,与键值一一对应
jianzhi=scan();
if(jianzhi==15)
{
sigh_save=1;
oled_fill(0x00);
break;
}
handle_1();
menu_location();
systick_delay_ms(STM0,5);//防止手抖
longpress();
cursor_location();
systick_delay_ms(STM0,8);//防止手抖
number_set(); //检测是否要输入数据
read_data(); //检测是否读取数据
refresh(); //刷新屏幕,更新数据
}
save_data();//储存数据
//获取参数
set_parameter();
}
一、延时
void delay(u16 i)// 延时函数,i=1时,大约延时10us
{
while(i--);
二、 scan()函数扫描获得键值
int scan(void)//扫描按下了哪个键,同时返回键值
{
char jp_h=0,jp_l=0;
int temp=0;
gpio_init(first_line, GPO, 0, PUSHPULL);//设置P20_8为输出 默认输出低电平 PUSHPULL:推挽输出
gpio_init(second_line, GPO, 0, PUSHPULL);
gpio_init(third_line, GPO, 0, PUSHPULL);
gpio_init(fourth_line, GPO, 0, PUSHPULL);
gpio_init(first_premolar, GPI, 1, PULLUP); //设置P21_2为输入 PULLUP:上拉输入
gpio_init(second_premolar, GPI, 1, PULLUP); //设置P21_2为输入 PULLUP:上拉输入
gpio_init(third_premolar, GPI, 1, PULLUP); //设置P21_2为输入 PULLUP:上拉输入
systick_delay_ms(STM0,50);
if( gpio_get (first_premolar)==0 || gpio_get (second_premolar)==0 || gpio_get (third_premolar)==0 ) //鍏堟娴嬫湁鏃犳寜閿寜涓�
{
systick_delay_ms(STM0,50);
if( gpio_get (first_premolar)==0 || gpio_get (second_premolar)==0 || gpio_get (third_premolar)==0) //鍘绘姈
{
if(gpio_get (first_premolar)==0 && gpio_get (second_premolar)!=0 && gpio_get (third_premolar)!=0)
jp_l=1;
else if(gpio_get (first_premolar)!=0 && gpio_get (second_premolar)==0 && gpio_get (third_premolar)!=0)
jp_l=2;
else if(gpio_get (first_premolar)!=0 && gpio_get (second_premolar)!=0 && gpio_get (third_premolar)==0)
jp_l=3;
gpio_init (first_line, GPI, 1,PULLUP);
gpio_init (second_line, GPI, 1,PULLUP);
gpio_init (third_line, GPI, 1,PULLUP);
gpio_init (fourth_line, GPI, 1,PULLUP);
gpio_init (first_premolar, GPO, 0,PUSHPULL);
gpio_init (second_premolar, GPO, 0,PUSHPULL);
gpio_init (third_premolar, GPO, 0,PUSHPULL);
systick_delay_ms(STM0,50);
if(gpio_get (first_line)==0 && gpio_get (second_line)!=0 && gpio_get (third_line)!=0 && gpio_get (fourth_line)!=0)
jp_h=1;
else if(gpio_get (first_line)!=0 && gpio_get (second_line)==0 && gpio_get (third_line)!=0 && gpio_get (fourth_line)!=0)
jp_h=2;
else if(gpio_get (first_line)!=0 && gpio_get (second_line)!=0 && gpio_get (third_line)==0 && gpio_get (fourth_line)!=0)
jp_h=3;
else if(gpio_get (first_line)!=0 && gpio_get (second_line)!=0 && gpio_get (third_line)!=0 && gpio_get (fourth_line)==0)
jp_h=4;
}
}
jp_h_show=jp_h;
jp_l_show=jp_l;
if(jp_h==0&&jp_l==0)
temp= 0;
else
temp= ((jp_h-1)*4+jp_l);
return temp;
}
矩阵键盘的每一行每一列都对应着一个IO口,当有按键被按下时,通过检测对应的IO口电平就能得知是哪一行哪一列的按键被按下。
具体方式:在检测列时,先将所有行的IO口都配置为低电平输出模式,所有列IO口都配置为输入模式(初始高电平),由原理图可知,当一个按键被按下时,对应列会由高电平转化位低电平,对于行的检测同理
在得到了对应按键的行(jp_h)、列(jp_l)之后,就可以通过固定的公式:
((行数-1)*4+列数)
算出jianzhi,即每个按键都对应着一个jianzhi,比如第一行第一列的按键被按下时,读取到的jianzhi为1。
三、键盘操作完成
jianzhi=15时 sign_save存储标志位置1,跳出主循环,即当左下角(第一列第四行)的键按下后,结束键盘主循环(键盘操作完成)。
四、将jianzhi通过handle函数再转化为i(一一对应)。
之所以再将jianzhi转化为i主要是为了后面确定输入的函数比较方便
注意:i是全局变量,所以如果要在键盘程序内添加自己的代码请避免用i作为参数,以免干扰其它程序,在keyboard.c以外不受影响。
五、menu_location()
确定当前屏幕显示的是第几页菜单(menu_lc),并实现通过按键进行的翻页操作。最大页后翻则自动回到最低页,最小页同理。
void menu_location() //确定第几页菜单
{
if( menu_lc<=menu_max && menu_lc>=menu_min ) //除特殊页外翻页轮转
{
if(i==4)
{
if(menu_lc==menu_min)
{
menu_lc=menu_max;
sigh_refresh=1;
oled_fill(0xff);
}
else
{
menu_lc--;
sigh_refresh=1;
oled_fill(0xff);
}
}
if(i==6)
{
if(menu_lc==menu_max)
{
menu_lc=menu_min;
sigh_refresh=1;
oled_fill(0xff);
}
else
{
menu_lc++;
sigh_refresh=1;
oled_fill(0xff);
}
}
if(sigh_refresh==1)
{
menu(menu_lc);
sigh_refresh=0;
}
}
else // ====================特殊页按翻页键退出特殊模式=======================
{
if(jianzhi==5 ||jianzhi==7)
menu_lc=menu_min;
else
;//menu_lc=menu_lc;
}
if(sigh_refresh==1)
{
menu(menu_lc);
sigh_refresh=0;
}
}
六、cursor_location()
确定光标的行数(curso_lc)并实现通过按键改变光标行数的操作,原理与menu_location基本相同。
void cursor_location() //确定光标的位置
{
if(i==2)
{
if(cursor_lc==cursor_min)
{
cursor_lc=cursor_max;
oled_p6x8str(70,cursor_min,"@");
}
else
{
cursor_lc--;
oled_p6x8str(70,cursor_lc+1,"@");
}
}
if(i==8)
{
if(cursor_lc==cursor_max)
{
cursor_lc=cursor_min;
oled_p6x8str(70,cursor_max,"@");
}
else
{
cursor_lc++;
oled_p6x8str(70,cursor_lc-1,"@");
}
}
oled_p6x8str(70,cursor_lc,"$");
}
修改参数模式
通过按下对应按键触发number_set函数里的while(1)循环,进入修改参数模式,函数比较复杂就不详细解释了(但大体逻辑并不复杂),需要注意的是:
修改参数是的输入过程是从高位向低位输入,每一页都对应一个该页位数
可以通过修改menu(x)_N改变对应页数的最大位。
void number_set() //输入数字的函数 (输入时要对应页数也就是menux_N,x为第x页)
{
if(i==111)
{
systick_delay_ms(STM0,10);
if(i==111) //去抖
{
int sigh_inputend=0,c,j=0; //c用来记录menu_lc
char sigh_delete[5]={0};
int blink=0; //用来让光标在输入的时候闪烁,输入满的时候就一直显示不动
systick_delay_ms(STM0,100);
number_input=0; //将储存输入数值的变量清空
/*switch(menu_lc)
{
case 0:{c=sigh_input=menu0_N-1;};break;
case 1:{c=sigh_input=menu1_N-1;};break;
case 2:{c=sigh_input=menu2_N-1;};break;
case 3:{c=sigh_input=menu3_N-1;};break;
case 4:{c=sigh_input=menu4_N-1;};break;
case 5:{c=sigh_input=menu5_N-1;};break;
}*/
c=sigh_input=menu_N-1;
i=0;
oled_p6x8str(cursor_select+17,cursor_lc,"____"); //清除进入编辑模式前的光标同时将进入输入模式后将原来的数字清除cursor_select-1
while(1) //输入数字的循环
{
if(sigh_inputend==1) //退出输入
{
oled_fill(0xff); //清除残留的显示
sigh_refresh=1; //发出刷新屏的指令
break;
}
/输入的时候显示光标/
if(sigh_input<0)
oled_p6x8str(cursor_set+6*(c-sigh_input),cursor_lc,"$");//输入完成后让光标一直显示不动
else
{
if(blink<1000)
oled_p6x8str(cursor_set+6*(c-sigh_input),cursor_lc,"$"); //显示待输入处的光标
if(blink>1000&&blink<2000)
oled_p6x8str(cursor_set+6*(c-sigh_input),cursor_lc,"@");
blink++;
if(blink==2000)
blink=0;
}
jianzhi=scan();
handle_1();
if(jianzhi==16) //撤销输入
{
break;
}
if(jianzhi==13) //确定后处理(和i=111等价)(先显示一个确定框,确定的话,进行赋值,重新显示;取消的话相当于撤销输入。这样可以将键盘变成4*3的,减少占地面积)
{
//傅永浩键盘
if(number_input> (pow(10,menu_N-1)-1) &&sigh_input==menu_N-2) number_input=(int)(number_input/pow(10,(sigh_input+1)));//TODO 输入大于100后自动减一
else number_input=(int)(number_input/pow(10,(sigh_input+1))+0.5);
/显示确定框/
oled_p6x8str(15,3,"Are You Sure ?");
oled_p6x8str(15,4," ");
oled_p6x8str(15,5,"Yes No");
systick_delay_ms(STM0,200);
do
{
jianzhi=scan();
if(jianzhi==0)
continue;
else
{
if(jianzhi==13)
continue;
else
{
if(jianzhi==15)
continue;
else
jianzhi=0;
}
}
}
while(jianzhi==0);
if(jianzhi==15)
{
oled_fill(0xff); //清除残留的显示
sigh_refresh=1;
break;
}
//
if(menu_lc==0)
{
switch(cursor_lc)
{
case 0:{menu_date[0]=(int)number_input;}break;
case 1:{menu_date[1]=(int)number_input;}break;
case 2:{menu_date[2]=(int)number_input;}break;
case 3:{menu_date[3]=(int)number_input;}break;
case 4:{menu_date[4]=(int)number_input;}break;
case 5:{menu_date[5]=(int)number_input;}break;
case 6:{menu_date[6]=(int)number_input;}break;
} //switch中case后面给谁赋值,与menu对应,这里可自己更改
} //menu_lc定位页数,cursor_lc定位行数,从而决定给谁赋值
//
if(menu_lc==1)
{
switch(cursor_lc)
{
case 0:{menu_date[7]=(int)number_input;}break;
case 1:{menu_date[8]=(int)number_input;}break;
case 2:{menu_date[9]=(int)number_input;}break;
case 3:{menu_date[10]=(int)number_input;}break;
case 4:{menu_date[11]=(int)number_input;}break;
case 5:{menu_date[12]=(int)number_input;}break;
case 6:{menu_date[13]=(int)number_input;}break;
}
}
if(menu_lc==2)
{
switch(cursor_lc)
{
case 0:{menu_date[14]=(int)number_input;}break;
case 1:{menu_date[15]=(int)number_input;}break;
case 2:{menu_date[16]=(int)number_input;}break;
case 3:{menu_date[17]=(int)number_input;}break;
case 4:{menu_date[18]=(int)number_input;}break;
case 5:{menu_date[19]=(int)number_input;}break;
case 6:{menu_date[20]=(int)number_input;}break;
}
}
if(menu_lc==3)
{
switch(cursor_lc)
{
case 0:{menu_date[21]=(int)number_input;}break;
case 1:{menu_date[22]=(int)number_input;}break;
case 2:{menu_date[23]=(int)number_input;}break;
case 3:{menu_date[24]=(int)number_input;}break;
case 4:{menu_date[25]=(int)number_input;}break;
case 5:{menu_date[26]=(int)number_input;}break;
case 6:{menu_date[27]=(int)number_input;}break;
}
}
if(menu_lc==4)
{
switch(cursor_lc)
{
case 0:{menu_date[28]=(int)number_input;}break;
case 1:{menu_date[29]=(int)number_input;}break;
case 2:{menu_date[30]=(int)number_input;}break;
case 3:{menu_date[31]=(int)number_input;}break;
case 4:{menu_date[32]=(int)number_input;}break;
case 5:{menu_date[33]=(int)number_input;}break;
case 6:{menu_date[34]=(int)number_input;}break;
}
}
if(menu_lc==5)
{
switch(cursor_lc)
{
case 0:{menu_date[35]=(int)number_input;}break;
case 1:{menu_date[36]=(int)number_input;}break;
case 2:{menu_date[37]=(int)number_input;}break;
case 3:{menu_date[38]=(int)number_input;}break;
case 4:{menu_date[39]=(int)number_input;}break;
case 5:{menu_date[40]=(int)number_input;}break;
case 6:{menu_date[41]=(int)number_input;}break;
}
}
if(menu_lc==6)
{
switch(cursor_lc)
{
case 0:{menu_date[42]=(int)number_input;}break;
case 1:{menu_date[43]=(int)number_input;}break;
case 2:{menu_date[44]=(int)number_input;}break;
case 3:{menu_date[45]=(int)number_input;}break;
case 4:{menu_date[46]=(int)number_input;}break;
case 5:{menu_date[47]=(int)number_input;}break;
case 6:{menu_date[48]=(int)number_input;}break;
}
}
//===================================特殊页面================================
if(menu_lc==20) // 1
{
switch(cursor_lc)
{
case 0:{menu_date[49]=(int)(number_input!=0);}break;
case 1:{menu_date[50]=(int)(number_input!=0);}break;
case 2:{menu_date[51]=(int)(number_input!=0);}break;
case 3:{menu_date[52]=(int)(number_input!=0);}break;
case 4:{menu_date[53]=(int)(number_input!=0);}break;
case 5:{menu_date[54]=(int)number_input;}break;
case 6:{menu_date[55]=(int)number_input;}break;
}
}
if(menu_lc==19) // 3
{
switch(cursor_lc)
{
case 0:{menu_date[56]=(int)number_input;}break;
case 1:{menu_date[57]=(int)number_input;}break;
case 2:{menu_date[58]=(int)number_input;}break;
case 3:{menu_date[59]=(int)number_input;}break;
case 4:{menu_date[60]=(int)number_input;}break;
case 5:{menu_date[61]=(int)number_input;}break;
case 6:{menu_date[62]=(int)number_input;}break;
}
}
if(menu_lc==18) // 7
{
switch(cursor_lc)
{
case 0:{menu_date[63]=(int)number_input;}break;
case 1:{menu_date[64]=(int)number_input;}break;
case 2:{menu_date[65]=(int)number_input;}break;
case 3:{menu_date[66]=(int)number_input;}break;
case 4:{menu_date[67]=(int)number_input;}break;
case 5:{menu_date[68]=(int)number_input;}break;
case 6:{menu_date[69]=(int)number_input;}break;
}
}
if(menu_lc==17) // 9
{
switch(cursor_lc)
{
case 0:{menu_date[70]=(int)number_input;}break;
case 1:{menu_date[71]=(int)number_input;}break;
case 2:{menu_date[72]=(int)number_input;}break;
case 3:{menu_date[73]=(int)number_input;}break;
case 4:{menu_date[74]=(int)number_input;}break;
case 5:{menu_date[75]=(int)number_input;}break;
case 6:{menu_date[76]=(int)number_input;}break;
}
}
//menu_lc定位页数,cursor_lc定位行数,从而决定给谁赋值
sigh_inputend=1; //==1表示输入完成
systick_delay_ms(STM0,200); //延时防抖
}
//
if(jianzhi==15&&sigh_input<menu_N-1) //删除功能
{
//led(LED3,LED_ON);
systick_delay_ms(STM0,200);
j-=1;
i=sigh_delete[j]-48;
sigh_delete[j]=0+48;
oled_p6x8str((cursor_set+6*(c-sigh_input)),cursor_lc,"@"); //删除后一位位光标
sigh_input+=1;
oled_p6x8str((cursor_set+6*(c-sigh_input)),cursor_lc,"$"); //添加前一位光标
number_input-=(i*pow(10,sigh_input)); //删除存储的数值
//led(LED3,LED_OFF);
}
//
if(condition_input&&sigh_input>=0) //输入数字 这里要区分页数
{
sigh_delete[j]=i+48;
j++;
number_input+=(i*pow(10,sigh_input));//存下万位
xianshi((cursor_set+(c-sigh_input)*6),cursor_lc,i); //原来的万位显示数字同时覆盖光标
sigh_input-=1;
oled_p6x8str((cursor_set+6*(c-sigh_input)),cursor_lc,"$"); //千位添加光标
systick_delay_ms(STM0,200); //强行拉慢输入速度,防止输入错误
}
}
//
}
//
}
}
参数处理
当read_flag=1(初始设置为1,读取后设置为0,之后就没有置1的地方了,所以可以理解位第一遍循环读取一次数据),进行读取数据的操作,具体进行了很多子函数的调用,最后就是这个read_all()函数,将数据从存储芯片中读取并赋值给menu_date。
void read_data() //进行读取数据的函数
{
if(sigh_read!=0)
{
oled_fill(0xff);
oled_p6x8str(10,3,"now is reading");
read_last();
oled_p6x8str(10,3,"read finished");
oled_fill(0xff);
sigh_read=0;
}
}
}
⑿ :在键盘操作结束后(修改参数)用save_data函数进行参数的保存,read_all的逆过程,不再赘述。
void save_data() //进行储存数据的函数
{
if((int)(sigh_save)!=0.0)
{
oled_fill(0xff);
oled_p6x8str(10,3,"now is saving");
systick_delay_ms(STM0,50);
save_all();
sigh_save=0;
oled_p6x8str(10,3,"save finished");
systick_delay_ms(STM0,50);
oled_fill(0xff);
}
}
参数命名
最后一步,set_parrmeter()将存储的数值赋值给对应参数。该部分将由用户自己定义,以下只展示其中一部分:
void set_parameter()
{
1
// Circle_flag =menu_date[0];
// bluetooth_send_data =menu_date[1];
// motor_flag =menu_date[2];
// DuojiLinear =menu_date[3];
// NMS_ON =menu_date[4];
// // Binary =menu_date[5];
// // Camera_protect =menu_date[6];
......................
//菜单显示设置/
//左侧项目显示
unsigned char menu_date_name_00[20]="[circle]";
unsigned char menu_date_name_01[20]="[send]";
unsigned char menu_date_name_02[20]="[motor]";
unsigned char menu_date_name_03[20]="[DjLinear]";
unsigned char menu_date_name_04[20]="[NMS_ON]";
unsigned char menu_date_name_05[20]="[Binary]";
unsigned char menu_date_name_06[20]="[Cam_protect]";