当前位置:网站首页>矩阵键盘&基于51(UcosII)计算器小项目

矩阵键盘&基于51(UcosII)计算器小项目

2022-08-10 12:34:00 不知足额

矩阵键盘

最近要给学弟学妹(应该没有)培训矩阵键盘,正好写一写我写矩阵键盘的思路,顺便分享一个之前做的基于ucosii的计算器小项目

原理

原理就不介绍了,CSDN有很多分享的,推荐两篇参考一下:

参考:矩阵键盘_丹山起凤的博客-CSDN博客_矩阵键盘
参考:一文带你详解矩阵键盘工作原理_Z小旋的博客-CSDN博客_矩阵键盘工作原理

代码

行列扫描法:

原理:

我习惯用x,y轴,把矩阵键盘抽象成二元函数

  1. 先对列扫描,行端口输出低电平,列端口输出高电平。读取列端口(是否被拉低)获取列值;

  2. 再对行扫描,列端口输出低电平,行端口输出高电平。读取行端口(是否被拉低)获取行值。

  3. 最后通过行值和列值计算得出键值。

PS:可通过方程组解出键值与行列值关系,参考下图设方程:ax+by+c=key_num(a,b,c为系数,x和y为行列值,key_num为键值,解出记得带入验证)

PS:行列坐标可自定义,只需要解出对应的计算公式即可

1 	2	3	4		S7	S11	S15	S19
5	6	7	8		S6	S10	S14	S18
9	10	11	12		S5	S9	S13	S17
13	14	15	16		S4	S8	S12	S16

如下图:

​ S7: 1 * a + 4 * b + c = 1; S4: 1 * a + 1 * b + c = 13; S16: 4 * a + 1 * b + c = 16

解得: x + 4 * y - 16 = key_num

矩阵键盘原理图

源码:

/*********4*4键盘扫描*********** 对应键值关系 1 2 3 4 S7 S11 S15 S19 5 6 7 8 S6 S10 S14 S18 9 10 11 12 S5 S9 S13 S17 13 14 15 16 S4 S8 S12 S16 在stc15上无P36 P37,对应的是P42 P44!!!!!!!! 函数返回值为0-16 ***************************/
unsigned char key_scanf()
{
    
	uchar x=0,y=0;
    //io口赋值
	P30=0;P31=0;P32=0;P33=0;//拉低行
	P34=1;P35=1;P42=1;P44=1;//拉高列
	if(!(P34&P35&P42&P44))//判断是否有按下
	{
    
		Delay20us();		//消抖
		if(!(P34&P35&P42&P44))//再次判断是否有按下
		{
    
			if(!P34)
				x=4;
			if(!P35)
				x=3;
			if(!P42)
				x=2;
			if(!P44)
				x=1;//获得列值
			P30=1;P31=1;P32=1;P33=1;//拉高行
			P34=0;P35=0;P42=0;P44=0;//拉低列
			if(!P30)
				y=1;
			if(!P31)
				y=2;
			if(!P32)
				y=3;
			if(!P33)
				y=4;//获取行值
			while(!(P30&P31&P32&P33));//等待释放
			return (x-4*y+16);	//计算键值
		}
	}
	return 0;//无按键按下返回0
}

计算方法2:

在使用Proteus仿真时,上一种计算方法效果不太好,不清楚原因,在网上查到了一种方法用仿真效果还可以。
仿真计算器按键
源码:

unsigned char code a[]={
    0xFE,0xFD,0xFB,0xF7};
//按键扫描函数
int Sacn_Key(void)
{
    
 	unsigned char row,col,i;
 	P1=0xf0;
 	if((P1&0xf0)!=0xf0)
 	{
    
	   	//delay(10);
   		if((P1&0xf0)!=0xf0)
		{
    
			row=P1^0xf0;          //确定行线
			i=0;
			P1=a[i];	          //精确定位
			while(i<4)
			{
    
	 			if((P1&0xf0)!=0xf0)
	  			{
    
	   				col=~(P1&0xff);	  //确定列线
	   				break;            //已定位后提前退出 
	  			}
				else 
	  			{
    
	   				i++;
	   				P1=a[i];
	  			}
			}
		}
		else 
		{
    
			return 0;
		}
	
		while((P1&0xf0)!=0xf0);

		return (row|col);	 		//行线与列线组合后返回
 	}
 	else return 0;	         		//无键按下时返回0
}

这种扫描方法可以配合一个解析程序分析按下的按键,我用的是switch函数实现,按键参考上图:

//解析按键扫描函数的值
char coding(int k)
{
    
	char res;
	switch (k)
	{
    
		case 0: res = 0; break;
		case 17:res = '7';break;
		case 18:res = '4';break;
		case 20:res = '1';break;
		case 24:res = ' ';break;
		case 33:res = '8';break;
		case 34:res = '5';break;
		case 36:res = '2';break;
		case 40:res = '0';break;
		case 65:res = '9';break;
		case 66:res = '6';break;
		case 68:res = '3';break;
		case 72:res = '=';break;
		case 129:res = '/';break;
		case 130:res = '*';break;
		case 132:res = '-';break;
		case 136:res = '+';break;
	}
	return res;
}

小项目:基于51的计算器:

简介:

基于Proteus仿真,51芯片,搭载ucosii。完整仿真工程及项目代码打包好会上传滴(会在文章末尾)。
仿真图
PS:1. 小黑框Virtual Terminal 在菜单栏Debug中最后一个哦

​ 2. 仿真要把串口波特率配置成19200(双击原理图上的那个外接的黑框框),点击ok

设计思路:

  1. 三个任务:计算任务,输出任务,按键任务

    • 计算任务:获得算式,传送结果
    • 输出任务:串口输出需要显示的内容
    • 按键任务:检测矩阵键盘,获取输入内容
  2. 结构体

    用于存储输入算式,由按键任务投递到计算任务

    typedef struct {
          
    	int num1;//数字1
    	int num2;//数字2
    	char cal;//运算符
    }my_cal_t;
    my_cal_t my_cc;
    
  3. 邮箱

    计算器结构体邮箱:用于按键函数输出计算任务给计算任务

    串口输出数据邮箱:串口输出数据邮箱,用于输出内容到虚拟串口

    OS_EVENT * Num_Box;//计算器结构体邮箱
    OS_EVENT * Mes_Box;//串口显示数据邮箱代码实现:
    

代码:

部分代码:

  1. 主函数

    //初始化系统,初始化外设,初始化任务,启动调度
    void main(void)
    {
          
        OSInit();
        
        InitTimer0();
        InitSerial();
        InitSerialBuffer();
    	
    	Num_Box = OSMboxCreate((void*)0);
    	Mes_Box = OSMboxCreate((void*)0);
        //矩阵键盘任务
        OSTaskCreate(TaskStartyya, (void *)0, &TaskStartStkyya[0],4);
    	//计算任务
        OSTaskCreate(TaskStartyyb, (void *)0, &TaskStartStkyyb[0],3);
    	//串口显示任务
    	OSTaskCreate(TaskStartyyc, (void *)0, &TaskStartStkyyc[0],2);
        
        OSStart();
    }
    
  2. 键盘任务

    //循环获取计算公式,投递至邮箱
    for(;;){
          
    		my_cc.num1 = 0;
    		my_cc.num2 = 0;
    		my_cc.cal = 0;
    		PrintStr("\nPlease enter num1 (end by /,*,+,-):\n");
    		//数字一及运算符输入
    		for(i = 0;i < 4;)
    		{
          //循环获取按键
    			temp = 0;
    			temp = Sacn_Key();
    			t = coding(temp);
    			//判断是否为运算符
    			if(t == '/'||t == '*'||t == '+'||t == '-')
    			{
          
    				sprintf(txt, "%c", t);
    				OSMboxPost(Mes_Box, txt);
    				my_cc.cal = t;
    				break;
    			}
    			//判断输入是否为数字
    			if(t <= '9'&&t >= '0')
    			{
          
    				num1[i] = t - 0x30;
    				sprintf(txt, "%d", num1[i]);
    				OSMboxPost(Mes_Box, txt);
    				i++;
    				if(i==4)
    				{
          
    					//输入满4位退出,获取运算符号
    					PrintStr("\nPlease enter /,*,+,-\n");
    					//循环等待运算符输入
    					while(1)
    					{
          
    						temp = Sacn_Key();
    						t = coding(temp);
    						if(t == '/'||t == '*'||t == '+'||t == '-')
    						{
          
    							my_cc.cal = t;
    							sprintf(txt, "%c", t);
    							OSMboxPost(Mes_Box, txt);
    							break;
    							//是运算符,退出数字一输入
    						}
    					}
    				}
    			}
    			OSTimeDlyHMSM(0,0,0,10);
    		}
    		//计算数字一
    		for(temp=0;temp<i;temp++)
    		{
          
    			my_cc.num1*=10;
    			my_cc.num1+=num1[temp];
    		}
    		//输入数字二
    		PrintStr("\nPlease enter num2 (end by =):\n");
    		for(i = 0;i < 4;)
    		{
          
    			temp = 0;
    			temp = Sacn_Key();
    			t = coding(temp);
    			//判断是否输入等于号
    			if(t == '=')
    			{
          
    				break;
    			}
    			//判断是否输入数字
    			if(t <= '9'&&t >= '0')
    			{
          
    				num2[i] = t - 0x30;
    				sprintf(txt, "%d", num2[i]);
    				OSMboxPost(Mes_Box, txt);
    				i++;
    			}
    			OSTimeDlyHMSM(0,0,0,10);
    		}
    		//计算数字二
    		for(temp=0;temp<i;temp++)
    		{
          
    			my_cc.num2*=10;
    			my_cc.num2+=num2[temp];
    			
    		}
    		//将结构体投入邮箱
    		OSMboxPost(Num_Box, &my_cc);
        }
    
  3. 计算任务

    //挂死等着按键任务唤醒,计算完了把结果投递输出任务
    for(;;){
          
    		//等待邮箱
    		cc = OSMboxPend(Num_Box,0,&err);
    		res = 0;
    		//PrintStr("\nok\n");
    		//根据运算符计算结果
    		switch (cc->cal)
    		{
          
    			case '/':if(cc->num2 == 0)
    					{
          	
    						sprintf(txt,"\nillegal value!\n",res);
    						OSMboxPost(Mes_Box, txt);
    						continue;
    					}
    					else{
          res=(float)cc->num1/(float)cc->num2;}break;
    			case '*':res=cc->num1*cc->num2;break;
    			case '+':res=cc->num1+cc->num2;break;
    			case '-':res=cc->num1-cc->num2;break;
    		}
    		//输出结果
    		sprintf(txt,"\nresult is %.02f\n",res);
    		OSMboxPost(Mes_Box, txt);   
        }    
    
  4. 输出任务

    //没啥实际意义,简单封装了的任务
    void TaskStartyyc(void *yydata) reentrant
    {
          
    	char *txt;
    	int err;
        yydata=yydata; 
        
        for(;;){
          
    		//接收邮件,并用串口打印
    		txt = OSMboxPend(Mes_Box,0,&err);
    		PrintStr(txt);
        }    
    }
    

感想

因为写的比较急,我自己都感觉代码很粗糙,很多地方可能还需要推敲;当然因为写的急,功能也很局限,只能进行双目计算。考虑到用的51系列芯片,还跑了ucosii,所以后期完善可能会换成32来仿真。这个工程我用的是移植好的,下载下来删删改改就用上了,不是自己移植的都很难修改,很多文件都有权限,所以经供参考了。

目前我的改进思路:

  1. 用栈代替结构体(万物皆为数据结构)
  2. 优化输出任务(还没想好,大体就是可以移植数码管或者其他显示方式)

仿真工程和项目源码正在打包上传中,可以关注公众号,发送:计算器,自动回复云盘链接。欢迎留言讨论~请添加图片描述

原网站

版权声明
本文为[不知足额]所创,转载请带上原文链接,感谢
https://blog.csdn.net/qq_45698227/article/details/126215766