前两篇博文大致的说明了矩阵按键的触发、显示及消抖。这篇博文是矩阵按键系列的最后一篇
用三个小应用分别是:简单加法器、加法器和幂运算(智能运算符合运算法则)、简单减法器。来巩固矩阵按键的用法。
函数的调用
在一个程序的编写过程中,随着代码量的增加,如果把所有的语句都写到main函数中,一方面程序会显得的比较乱,另一方面,当同一个功能需要在不同的地方执行时,就得再重复写一遍相同的语句。此时。如果把一些零碎的功能单独写成一个函数,在需要他们的时候只需进行简单的函数的调用,这样既有助于程序结构的清晰条理,又可以避免大块的代码重复。
51单片机的中断服务函数,不需要也不能,由其它函数调用。
函数调用的一般形式是:
函数名(实参列表)
函数名就是需要调用的函数的名称,实参列表就是根据实际需求,调用函数要传递给被调用函数的参数列表。不需要传递参数时只保留括号就可以了,传递多个参数时,参数之间要用逗号隔开。
1:函数调用的时候,不需要加函数类型(不需要加类似void int函数前面那个定义符)
2:调用函数与被调用函数的位置关系。
函数在调用前,必须先被定义或者声明。意思是一个函数应该先定义,然后才能被调用,也就是说调用函数应该位于被调用函数的下方,这种调用函数不需在主函数前声明。还有一种就是在主函数前声明一下。
3:函数声明的时候必需加函数类型,函数的形式参数,最后加一个分号表示结束。
函数声明行与函数定义行的唯一区别就是最后的分号,其他的都必须保持一致。这点尤其重要
函数的形参和实参
形参(形式参数)是在函数或方法定义中用来接收参数值的变量。它们在函数或方法调用时被实参(实际参数)的值赋予,从而在函数或方法体内使用这些值来完成特定的操作。形参在函数或方法定义中声明,可以有多个,每个形参用逗号分隔。形参可以有指定的数据类型,也可以是泛型类型。形参的定义可以包括参数名和数据类型,例如:int x, String name, T item等。
实参是在函数调用时传递给函数的输入值,它们是函数定义中的形参的具体值。实参可以是常量、变量、表达式或者其他函数调用的返回值。通过将实参传递给函数,函数可以使用这些值来执行特定的操作。比如,在下面的函数调用中,1和2就是实参:
def add(a, b):
return a + b
result = add(1, 2)
(1)函数定义中指定的形参,在未发生函数调用时不占用内存,只有函数调用时函数中的形参才被分配内存单元。在调用结束后,形参占用的内存单元也被释放,形参是局部变量。
(2)实参可以是常量,也可以是简单或者复杂的表达式,但要求它们必须要有确定的值。
(3)形参必须要指定数据类型和定义变量一样,因为它本来就是局部变量
(4)实参和形参的数据类型应该相同或者赋值兼容。和变量赋值一样,当形参和实参出现不同类型时,按照不同类型数值的赋值规则进行转换。
(5)主调函数在调用函数前,应对被调用函数作原型声明
(6)实参向形参的数据传递是单向传递,不能由形参再传回实参。也就是说,实参传递给形参后,调用结束,形参单元被释放,而实参单元仍保留并维持原值。
简单加法计算器
先说下这个加法器的特点,有数字键0-9,加号键(向上键)、等号键(回车键)、清零复位键(ESC键),共13个有效键位。
能完成正整数之间的加法运算,有连加功能。
看代码
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code LedChar[] ={
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, //数码管显示字符转换表
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, //数码管显示缓冲区
};
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ 0x31,0x32,0x33,0x26}, //数字键1,数字键2,数字键3,向上键
{ 0x34,0x35,0x36,0x25}, //数字键4,数字键5,数字键6,向左键
{ 0x37,0x38,0x39,0x28}, //数字键7,数字键8,数字键9,向下键
{ 0x30,0x1B,0x0D,0x27} //数字键0,ESC键, 回车键, 向右键
};
unsigned char KeySta[4][4] = { //全部按键当前态
{1,1,1,1},
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
void KeyDriver(); //按键驱动函数声明
void ShowNumber(unsigned long num);
//void KeyAction(unsigned char keycode);
//void KeyScan();
//void LedScan();
void main()
{
EA = 1; //中断使能
ENLED = 0; //选择数码管显示
ADDR3 = 1;
//ADDR2 = 0;
//ADDR1 = 0;
//ADDR0 = 0;
TMOD = 0x01; //设置T0为模式1
TH0 = 0XFC; //定时1ms
TL0 = 0x67;
ET0 = 1; //启动定时器0中断
TR0 = 1;
LedBuff[0] = LedChar[0]; //上电显示0
while(1)
{
KeyDriver(); // 调用按键驱动函数
}
}
/*按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
static unsigned long result = 0; //用于保存运算结果
static unsigned long addend = 0; //用与保存输入加数
if((keycode >= 0x30) && (keycode <= 0x39)) //输入0-9的数字
{
addend = (addend * 10) + (keycode - 0x30); // 整体十进制左移,新数字进入个位
ShowNumber(addend); //运算结果显示到数码管
}
else if(keycode == 0x26) //向上键用作加号,执行加法或者连加运算
{
result += addend; //进行加法运算
addend = 0;
ShowNumber(result); // 运算结果显示到数码管
}
else if(keycode == 0x0D) //回车键执行加法运算(实际效果与加法相同)
{
result += addend; //进行加法运算
addend = 0;
ShowNumber(result); //运算结果显示到数码管
}
else if(keycode == 0x1B) //ESC键,清零结果
{
addend = 0;
result = 0;
ShowNumber(addend); //清零后的加数显示到数码管
}
}
/*将一个无符号长整形的数字显示到数码管上,num为代显示数字 */
void ShowNumber(unsigned long num)
{
signed char i;
unsigned char buf[6];
for(i = 0; i < 6; i++) //把长整型数转换为6位十进制的数组
{
buf[i] = num % 10;
num = num / 10;
}
for(i = 5; i >= 1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环
{
if(buf[i] == 0)
{LedBuff[i] = 0xFF;}
else
break;
}
for( ; i >=0; i--)
{
LedBuff[i] = LedChar[buf[i]]; //赋值需显示数码管的值
}
}
/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主循环中调用 */
void KeyDriver()
{
unsigned char i,j ;
static unsigned char PastSta[4][4] ={
{1,1,1,1}, //按键值备份,保存前一次的值(前一稳态)
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
for(i=0; i<4; i++) //循环检测4*4的矩阵按键,i是行 j是列
{
for(j=0; j<4; j++)
{
if(PastSta[i][j] != KeySta[i][j]) //检测按键动作
{
if(PastSta[i][j] != 0) //按键前一稳态不是0即按键前一稳态是1即现稳态是0,即按住开关触发
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
PastSta[i][j] = KeySta[i][j]; //把现稳态赋值给前态
}
}
}
}
/*按键动作扫描函数,需要定时中断调用,间隔1ms */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区,16个建初始都是0xff则说明全部处于弹起状态
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF}
};
//将一行4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0]<<1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1]<<1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2]<<1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3]<<1) | KEY_IN_4;
//消抖后更新按键状态
for(i = 0; i<4; i++) //每行4个按键,所以循环4次
{
if((keybuf[keyout][i] & 0x0F) ==0x00)
{ //连续4次扫描值为0,即4*4ms内部都是按下的状态时,可以认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if((keybuf[keyout][i] & 0x0F) ==0x0F)
{ //连续扫描4次扫描值为1,即4*4ms内部都是弹起状态,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
//else{}
}
keyout++; //输出值索引递增
keyout = keyout & 0x03; //索引值加到4即归零
switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0:KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1:KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2:KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3:KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default : break;
}
}
/*数码管动态扫描刷新,需在定时中断中调用 */
void LedScan()
{
static unsigned char i = 0; //动态扫描索引
P0 = 0xFF; //显示消隐
switch(i)
{
case 0: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[0]; break;
case 1: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = LedBuff[1]; break;
case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = LedBuff[2]; break;
case 3: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = LedBuff[3]; break;
case 4: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[4]; break;
case 5: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5]; break;
default: break;
}
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void interruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
LedScan(); //调用数码管显示扫描函数
KeyScan(); //调用按键扫描方式
}
测试程序是否正确的方法:(1)按键依次按1+2+34+567+8+9= 621 esc
(2)测试其他按键是否会发生干扰,按键依次按 + = 1 + = 5 + = 6 + 6 = = 18
看视频结果:加法运算_哔哩哔哩_bilibili
可以看到程序正常运行,也没受到其他按键的干扰。接着分析下程序的各个功能模块
主函数:初始化寄存器0的值,确定工作模式,开启中断,确定一上电显示的值(为0),然后一直循环执行KeyDriver函数 。
KeyDriver函数的功能就是检测是否有按键按下,获得按下按键的确定位置信息[i][j],再通过位置信息[i][j]映射在KeyCodeMap上获得相同位置信息[i][j]的值(实参),给KeyAction(函数。)
而KeyDriver函数在进行按键状态实时比较时,需要的按键状态值矩阵数组,由 KeyScan() 函数 ,定时器0中断函数一起提供。该详细工作原理可以查看上篇博文初学51单片机矩阵按键与消抖2-CSDN博客
keyAction函数的功能:就是把输入的按键值,经过合适的程序设计,获得一个符合运算要求的值,这个值,分为三类:1:输入的操作数(在本函数就是,加数和被加数) 2:中间值(对于本函数来说是加法运算的和,只是这个和不是最终的和是运算中间的和) 3:加法运算最终的值
把这三类值都传递给函数ShowNumber(unsigned long num),该函数的功能就是把这些值,在数码上显示出来。而显示些值是通过自身函数,LedScan(),中断函数一起作用的。
数码管动态显示可以查看之前的博文初学51单片机的灵魂中断以及数码管与C语言实践-CSDN博客
有详细的说明。
对于ShowNumber()函数本身,取值模块函数又和之前的博文里有些区别,这篇是稍微精巧了些。
对取值模块
以数字135为例,最终的结果是矩阵buf[6] ={5,3,1,0,0,0} buf[0] = 5 buf[5] = 0
这个结果\通过一些处理,最终在数码管上显示出来。
至此函数的所有模块都分析完毕。基本上都涵盖了之前博文的内容,。我用一个函数模块导图总结一下。
第二个是有加法和幂运算的计算器
先看代码:
#include<reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4 = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;
unsigned char code LedChar[] ={
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, //数码管显示字符转换表
0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6] = {
0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, //数码管显示缓冲区
};
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
{ 0x01,0x02,0x03,0x0A}, //数字键1,数字键2,数字键3,向上键
{ 0x04,0x05,0x06,0x0B}, //数字键4,数字键5,数字键6,向左键
{0x07,0x08,0x09,0x0C}, //数字键7,数字键8,数字键9,向下键
{0x00,0x0E,0x0F,0x0D} //数字键0,ESC键, 回车键, 向右键
};
unsigned char KeySta[4][4] = { //全部按键当前态
{1,1,1,1},
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
void KeyDriver(); //按键驱动函数声明
void ShowNumber(unsigned long num);
//void KeyAction(unsigned char keycode);
//void KeyScan();
//void LedScan();
void main()
{
EA = 1; //中断使能
ENLED = 0; //选择数码管显示
ADDR3 = 1;
//ADDR2 = 0;
//ADDR1 = 0;
//ADDR0 = 0;
TMOD = 0x01; //设置T0为模式1
TH0 = 0XFC; //定时1ms
TL0 = 0x67;
ET0 = 1; //启动定时器0中断
TR0 = 1;
LedBuff[0] = LedChar[0]; //上电显示0
while(1)
{
KeyDriver(); // 调用按键驱动函数
}
}
/*按键数值转换函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
static unsigned long result = 0; //操作结果数
static unsigned long transfer = 0; //操作中间数
static unsigned long i = 0; // 输入操作数个数记录
unsigned long j = 0;
static unsigned char sup = 0; // 指数运算上标标志
static unsigned char plus = 0; // 加法运算标志
unsigned long exp = 1; //第一次指数运算赋值
if((keycode >=0x00) && (keycode <= 0x09)) //操作数计数 (1-9为操作数 其他为运算符)
{
i++;
}
if((keycode == 0x0D) && (i >= 1) ) // 当已存在操作数且按下上标键 上标键置为0
{
sup = 1;
}
if (sup == 0 && plus == 0) //判断不存在任何运算符号的存在
{
if((keycode >=0x00) && (keycode <= 0x09)) //输入0-9的数字
{
result = (result*10) + (keycode - 0x00); // 整体十进制左移,新数字进入个位
ShowNumber(result); // 操作数运算结果显示到数码管
}
}
if((i > 1) && (plus >= 1) &&(sup == 0)) //判断只存在加号的标记不存在指数的符号
{
if((keycode >=0x00) && (keycode <= 0x09)) //加数执行 数码管显示,结果存入中间数 两项操作
{
transfer = (transfer * 10) + (keycode - 0x00);
ShowNumber( transfer);
}
}
if((i > 1) && (sup == 1) && (plus >= 1)) //判断存在多种运算符,按照混合运算法则计算
{
if((keycode >=0x00) && (keycode <= 0x09)) //输入0-9的数字
{
for(j = 1; j <= (keycode-0x00);j++) //指数运算
{
exp = exp * transfer;
}
if(keycode == 0) //指数上标为0,则值为其本身
{
exp = transfer;
}
transfer = exp; //指数运算结果送入中间数
ShowNumber( transfer); //显示指数运算结果
sup = 0;
}
}
if((keycode == 0x0A) && (i >= 1) ) //加法,以及连加运算标志
{
plus ++;
}
if((i > 1) && (sup == 1) && (plus == 0)) // 只存在指数运算符号,只进行指数运算
{
if((keycode >=0x00) && (keycode <= 0x09) ) //输入0-9的数字
{
for(j = 1; j <= (keycode-0x00);j++) //指数运算
{
exp = exp * result;
}
if(keycode == 0)
{ //指数上标为0,则值为其本身
exp = result;
}
result = exp; //指数运算直接赋值给结果
ShowNumber(result); //指数结果显示到数码管
sup = 0; //
}
}
if(keycode ==0x0A && plus >= 2) //存在连加,第二个往后的加号都有求和的功能
{
result = (result + transfer) ; //结果数与中间数相加的值给结果数
ShowNumber(result); //显示连加的结果
transfer = 0;
}
if(keycode == 0x0F ) // 按空格等同于“=”进行加法运算
{
result = (result + transfer) ; // 加法求和
ShowNumber(result); //
transfer = 0; //用“=”求和中间数清0
plus = 0; // 用“=”求和中间加号清0
}
if(keycode == 0x0E) // ESC 初始化清0
{
result = 0;
transfer = 0;
i = 0;
sup = 0;
plus = 0;
exp = 1;
ShowNumber(result);
}
}
/*void KeyAction(unsigned char keycode)
{
static unsigned long result = 0; //用于保存运算结果
static unsigned long addend = 0; //用与保存输入加数
if((keycode >=0x00) && (keycode <= 0x09)) //输入0-9的数字
{
addend = (addend*10) + (keycode - 0x00); // 整体十进制左移,新数字进入个位
ShowNumber(addend); //运算结果显示到数码管
}
else if(keycode == 0x0A) //向上键用作加号,执行加法或者连加运算
{
result = result + addend; //进行加法运算
addend = 0;
ShowNumber(result); // 运算结果显示到数码管
}
else if(keycode == 0x0F) //回车键执行加法运算(实际效果与加法相同)
{
result += addend; //进行加法运算
addend = 0;
ShowNumber(result); //运算结果显示到数码管
}
else if(keycode == 0x0E) //ESC键,清零结果
{
addend = 0;
result = 0;
ShowNumber(addend); //清零后的加数显示到数码管
}
} */
/*将一个无符号长整形的数字显示到数码管上,num为代显示数字 */
void ShowNumber(unsigned long num)
{
signed char i;
unsigned char buf[6];
for(i = 0;i < 6; i++) //把长整型数转换为6位十进制的数组
{
buf[i] = num % 10;
num = num / 10;
}
for(i = 5; i >= 1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环
{
if(buf[i] == 0)
LedBuff[i] = 0xFF;
else
break;
}
for( ; i >=0; i--)
{
LedBuff[i] = LedChar[buf[i]];
}
}
/*按键驱动函数,检测按键动作,调度相应动作函数,需要在主循环中调用 */
void KeyDriver()
{
unsigned char i,j ;
static unsigned char PastSta[4][4] ={
{1,1,1,1}, //按键值备份,保存前一次的值(前一稳态)
{1,1,1,1},
{1,1,1,1},
{1,1,1,1}
};
for(i=0; i<4; i++) //循环检测4*4的矩阵按键
{
for(j=0; j<4; j++)
{
if(PastSta[i][j] != KeySta[i][j]) //检测按键动作
{
if(KeySta[i][j] == 0) //按键前一稳态不是0即按键前一稳态是1即现稳态是0,即按住开关触发
{
KeyAction(KeyCodeMap[i][j]); //调用按键动作函数
}
PastSta[i][j] = KeySta[i][j]; //把现稳态赋值给前态
}
}
}
}
/*按键动作扫描函数,需要定时中断调用,间隔1ms */
void KeyScan()
{
unsigned char i;
static unsigned char keyout = 0; //矩阵按键扫描输出索引
static unsigned char keybuf[4][4] = { //矩阵按键扫描缓冲区,16个建初始都是0xff则说明全部处于弹起状态
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF},
{0xFF,0xFF,0xFF,0xFF}
};
//将一行4个按键值移入缓冲区
keybuf[keyout][0] = (keybuf[keyout][0]<<1) | KEY_IN_1;
keybuf[keyout][1] = (keybuf[keyout][1]<<1) | KEY_IN_2;
keybuf[keyout][2] = (keybuf[keyout][2]<<1) | KEY_IN_3;
keybuf[keyout][3] = (keybuf[keyout][3]<<1) | KEY_IN_4;
//消抖后更新按键状态
for(i = 0; i<4; i++) //每行4个按键,所以循环4次
{
if((keybuf[keyout][i] & 0x0F) ==0x00)
{ //连续4次扫描值为0,即4*4ms内部都是按下的状态时,可以认为按键已稳定的按下
KeySta[keyout][i] = 0;
}
else if((keybuf[keyout][i] & 0x0F) ==0x0F)
{ //连续扫描4次扫描值为1,即4*4ms内部都是弹起状态,可认为按键已稳定的弹起
KeySta[keyout][i] = 1;
}
else{}
}
keyout++; //输出值索引递增
keyout = keyout & 0x03; //索引值加到4即归零
switch(keyout) //根据索引,释放当前输出引脚,拉低下次的输出引脚
{
case 0:KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
case 1:KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
case 2:KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
case 3:KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
default : break;
}
}
/*数码管动态扫描刷新,需在定时中断中调用 */
void LedScan()
{
static unsigned char i = 0; //动态扫描索引
P0 = 0xFF; //显示消隐
switch(i)
{
case 0: ADDR2 = 0; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[0]; break;
case 1: ADDR2 = 0; ADDR1 = 0; ADDR0 = 1; i++; P0 = LedBuff[1]; break;
case 2: ADDR2 = 0; ADDR1 = 1; ADDR0 = 0; i++; P0 = LedBuff[2]; break;
case 3: ADDR2 = 0; ADDR1 = 1; ADDR0 = 1; i++; P0 = LedBuff[3]; break;
case 4: ADDR2 = 1; ADDR1 = 0; ADDR0 = 0; i++; P0 = LedBuff[4]; break;
case 5: ADDR2 = 1; ADDR1 = 0; ADDR0 = 1; i = 0; P0 = LedBuff[5]; break;
default: break;
}
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void interruptTimer0() interrupt 1
{
TH0 = 0xFC; //重新加载初值
TL0 = 0x67;
LedScan(); //调用数码管显示扫描函数
KeyScan(); //调用按键扫描方式
}
从程序可以看到,只有两处发生了变化一处是键盘的标准键码
这里我并没有采用按键的标准键码,而是直接用16进制的0-15作为按键的键码,物理键盘1映射的键码值也是1,事实上这里你用10进制的0-15也是没问题的。
从标准键码的数值上我们可以看出,它的值也不是随意取的,9(0x39)与0(0x30)之间的差值也是9,他们之间的顺序大小是符合现实自然数的。当然用标准键码主要是为了向行业或者标准看齐。
二是:KeyAction()函数这里几乎和之前单纯的加法运算器区别很大,作了非常多的判断。主要是这个函数是我自己,靠着逻辑强写然后慢慢调试出来的。它的扩展性不大,再加个符号的话可能要很多的判断,一下子不太好梳理。主要原因是它是符合混合运算规则的。
keyAction()
void KeyAction(unsigned char keycode)
{
static unsigned long result = 0; //操作结果数
static unsigned long transfer = 0; //操作中间数
static unsigned long i = 0; // 输入操作数个数记录
unsigned long j = 0;
static unsigned char sup = 0; // 指数运算上标标志
static unsigned char plus = 0; // 加法运算标志
unsigned long exp = 1; //第一次指数运算赋值
if((keycode >=0x00) && (keycode <= 0x09)) //操作数计数 (1-9为操作数 其他为运算符)
{
i++;
}
if((keycode == 0x0D) && (i >= 1) ) // 当已存在操作数且按下上标键 上标键置为0
{
sup = 1;
}
if (sup == 0 && plus == 0) //判断不存在任何运算符号的存在
{
if((keycode >=0x00) && (keycode <= 0x09)) //输入0-9的数字
{
result = (result*10) + (keycode - 0x00); // 整体十进制左移,新数字进入个位
ShowNumber(result); // 操作数运算结果显示到数码管
}
}
if((i > 1) && (plus >= 1) &&(sup == 0)) //判断只存在加号的标记不存在指数的符号
{
if((keycode >=0x00) && (keycode <= 0x09)) //加数执行 数码管显示,结果存入中间数 两项操作
{
transfer = (transfer * 10) + (keycode - 0x00);
ShowNumber( transfer);
}
}
if((i > 1) && (sup == 1) && (plus >= 1)) //判断存在多种运算符,按照混合运算法则计算
{
if((keycode >=0x00) && (keycode <= 0x09)) //输入0-9的数字
{
for(j = 1; j <= (keycode-0x00);j++) //指数运算
{
exp = exp * transfer;
}
if(keycode == 0) //指数上标为0,则值为其本身
{
exp = transfer;
}
transfer = exp; //指数运算结果送入中间数
ShowNumber( transfer); //显示指数运算结果
sup = 0;
}
}
if((keycode == 0x0A) && (i >= 1) ) //加法,以及连加运算标志
{
plus ++;
}
if((i > 1) && (sup == 1) && (plus == 0)) // 只存在指数运算符号,只进行指数运算
{
if((keycode >=0x00) && (keycode <= 0x09) ) //输入0-9的数字
{
for(j = 1; j <= (keycode-0x00);j++) //指数运算
{
exp = exp * result;
}
if(keycode == 0)
{ //指数上标为0,则值为其本身
exp = result;
}
result = exp; //指数运算直接赋值给结果
ShowNumber(result); //指数结果显示到数码管
sup = 0; //
}
}
if(keycode ==0x0A && plus >= 2) //存在连加,第二个往后的加号都有求和的功能
{
result = (result + transfer) ; //结果数与中间数相加的值给结果数
ShowNumber(result); //显示连加的结果
transfer = 0;
}
if(keycode == 0x0F ) // 按空格等同于“=”进行加法运算
{
result = (result + transfer) ; // 加法求和
ShowNumber(result); //
transfer = 0; //用“=”求和中间数清0
plus = 0; // 用“=”求和中间加号清0
}
if(keycode == 0x0E) // ESC 初始化清0
{
result = 0;
transfer = 0;
i = 0;
sup = 0;
plus = 0;
exp = 1;
ShowNumber(result);
}
}
以以下例子来判读程序的是否确实无误:指数与加法_哔哩哔哩_bilibili
(1)单纯加法运算:1+2+3+4+5=15 ESC (2)指数运算:2^2^3=64 ESC
(3)混合运算: 2^3+5=13 ESC (4)混合运算:5+2^3 =13 ESC
(5)混合运算 2^2^3+5^2+3=92 ESC (6) 符号优先级:5+^2=25 (符合操作逻辑(误操作了) 但不符合运算逻辑) 失败(该现象 没有在程序中描述因此失败了结果是0)
(7)5^+2=7 (依然失败了) (8)2+++3=5 (9) 2^^^3=8(10)^2+5=7 (11) +5^2=25
(12) ^+5^2 = 25 (13)12^2=144
(14) 2^10 =1024(失败,只能作个位数的指数运算) 函数没有描述怎么对两位以上的指数运算
可以看到程序只对输入两个不同符号才发生报错。看来如果要分辨误操作,还必须对连续输入符号进行判断处理,程序某些逻辑要重写。
也就是说如果你输入的是一个符号键码,这个键码按照我这个编程逻辑来说它不能马上的进入程序判读(当然等号键和ESC键除外),它必须等到下个按键是完整数字键,不是符号键才能开始程序工作。要确定是否是完整数字键,则必须是该完整数字键最后位接了一个符号键。(即 符号键-多个数字键-符号键)如此才能确定数字键输入完毕。
或者当程序检测到上一个按键是符号键能清理上一个按键造成的影响,只对目前按键影响。
鉴于笔者C语言水平不高,大概只能写到这种程度了。
简单减法器
减法器和加法器相似区别是,减法运算最后的值有可能是负数。因此主要区别是数码管显示负数怎么处理:也就是
keyAction()void ShowNumber(unsigned long num)这里面的内容有所区别,其他部分的函数都一样。看代码
void KeyAction(unsigned char keycode)
{
static signed long result = 0; //用于保存运算结果
static signed long subtract = 0; //用与保存输入减数
static unsigned long i = 0;
if((keycode >= 0x30) && (keycode <= 0x39)) //输入0-9的数字
{
subtract = (subtract * 10) + (keycode - 0x30); // 整体十进制左移,新数字进入个位
ShowNumber(subtract); //运算结果显示到数码管
}
else if(keycode == 0x28) //向下键用作加号,执行减法或者连减运算
{
result = result - subtract ; //进行减法运算
subtract = 0;
i++;
if(i == 1)
{
result = (-result);
}
ShowNumber(result); // 运算结果显示到数码管
}
else if(keycode == 0x0D) //回车键执行最终运算(实际效果与减法相同)
{
result = result - subtract ; //进行减法运算
subtract = 0;
i++;
if(i == 1)
{
result = (-result);
}
ShowNumber(result); //运算结果显示到数码管
}
else if(keycode == 0x1B) //ESC键,清零结果
{
subtract = 0;
result = 0;
i = 0;
ShowNumber(subtract); //清零后的加数显示到数码管
}
}
void ShowNumber(signed long num)
{
signed char i;
unsigned char buf[6];
if(num >= 0)
{
for(i = 0; i < 6; i++) //把长整型数转换为6位十进制的数组
{
buf[i] = num % 10;
num = num / 10;
}
for(i = 5; i >= 1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环
{
if(buf[i] == 0)
{LedBuff[i] = 0xFF;}
else
break;
}
for( ; i >=0; i--)
{
LedBuff[i] = LedChar[buf[i]];
}
}
if(num < 0)
{
num = (-num); //负数转成正数显示
for(i = 0; i < 6; i++) //把长整型数转换为6位十进制的数组
{
buf[i] = num % 10;
num = num / 10;
}
for(i = 5; i >= 1; i--) //从最高位起,遇到0转换为空格,遇到非0则退出循环
{
if(buf[i] == 0)
{LedBuff[i] = 0xFF;}
else
break;
}
LedBuff[(i+1)] = 0xBF; //剩余低位前面加个负号
for( ; i >=0; i--) //剩余低位如实转换为数码管显示字符
{
LedBuff[i] = LedChar[buf[i]];
}
}
}
可以看到减法运算的值的显示逻辑是:先按照正整数显示,处理好结果后在正整数前面加个负号:
LedBuff[(i+1)] = 0xBF; 这句就是在加符号的语句。这个负号是怎么算出来的可以参考之前的博文真值表计算。初学51单片机定器数码管及C语言实践_51单片机定时器控制数码管程序-CSDN博客文章浏览阅读931次,点赞29次,收藏25次。TL0 = 0x00;if(shift1 == 0x01 && shift2 == 0x80) //判断循环变量1到最右端且循环变量2到最 左端。if(shift1 ==0x80 && shift2 == 0x01) //循环变量都移到最左端和最右端。if(shift1 == 0x08 && dir1 == 0) //判断是否 shit1运动到端口4并且是向左移动。if(shift1 == 0x10 && dir1 == 1) //判断是否 shift1运动到端口5并且是向右运动。_51单片机定时器控制数码管程序https://blog.csdn.net/firewood2024/article/details/137637292看下结果视频:简单减法器_哔哩哔哩_bilibili
9 - 6 -3 - 4 = -4
可以看到减法功能正常还能进行连减。
至此矩阵应用完结,事实上对于那个混合运算法则,以笔者目前的C语言水平写成来难度会比较大不过思路倒是有一些。
首先我们指定混合运算的逻辑是:
(1)预设的混合运算的符号有:加号 减号 乘法 除法 指数 负号 6个符号它们的特点是键码值都大于 0-9数字键的键码值,因此可以通过判断大小来判定是符号键还是数字键
(2)混合运算的运算法则是:1:先进行指数运算 2:对于乘法和除法优先级相同,则依次从左往右计算 3:对于加法和减法优先级相同也是最低的,其他符号运算结束后,按从左往右运算。 最后得到这个混合运算的值。
(3)从上述运算法则可以看出,如果按部就班的每个值每个值这样判断过去,程序将变得非常的复杂,就以加法和指数运算器可以看出是比较麻烦的,当然也有可能是笔者水平太菜。
(4)对于这个符合逻辑的计算器来说,判断一个完整的输入数是比较麻烦的,要完整的确定输入数输入完毕必须的要求是输入数前后都发现符号,此时输入数才完毕。
还有一种是混合运算的第一个输入数,它是没有符号的不同之处。
第三步是对输入数的十进制处理,本函数的是由下面这个函数处理的
当然对于混合运算来说这个可能要做调整。
(5)我对混合运算提出的解决方式是引入一个数组 unsigned char code[100],每输入一个键码,都储存到数组里,即 code[] = keycode; i++; 并且通过i++来计数。直到输入“=”的键码,程序开始工作计算最终的结果显示到数码管。
但是对于计算器来说每输入一个数都得显示到数码管上,由于是混合运算因此在输入过程中不会产生任何结果,只会显示输入的数值(不显示符号),直到等于号出现,程序的计算模块才开始工作。
计算模块:
第一步:输入结束以后,先确定符号键的位置。可以通过比大小确定符号键是数组的哪个位置,并且把符号键按次序存到一个新的数组里char sign[50].也就是说这个程序只允许50个运算符号之间的运算。多了就出错。这个通过for语句配合if语句是可以完成的。
并且判断是否存在负号键,把负号键的下标赋值给negsign,数组里则不存入负号键。
第二步:对输入的数字键处理处理,使之转化成十进制的数并依序出入新的数组 sign float operand[50]
第三步:通过计算找出负号下标的位置的数组元素对其进行处理
第四步:然后对两个数组处理计算求出最终的值。
我们举个例子 12 *3+5 - (-1)+12^3 / 14 + 6 = 由这个运算式子可知
sign[] = {*,+,-,+,^,/,+,=}
operand[] = {12,3,5,-1,12,3,14,6}
可以看到数字和符号的个数是一样多的。
接着:
(1)先判断是否存在指数符号,如果存在就进行指数运算,通过判断得知i是指数符号的下标
sign[i] 则这个指数运算就是:result = operand[i]^operand[i+1] =1728
对于示例来说 i = 4 对于operand[]数组来说operand[4]=12 operand[5]=3 是符合我们要求的
然后把 operand[i] = result operand[i+1] = result 数组更新。
operand[] = {12,3,5,-1,1728,1728,14,6}
(2)然后从左到右判断乘号与除号的位置 i= 0是乘号 先运算
result = operand[i] * operand[i+1] =36
operand[i] = result operand[i+1] = result
operand[] = {36,36,5,-1,1728,1728,14,6}
然后是除法 i=5
result = operand[i] / operand[i+1] =123.43(这个是约数)
operand[i] = result operand[i+1] = result
operand[] = {36,36,5,-1,1728,123.43,123.43,6}
(3)然后从左到右判断加法与减法
先进行加法 i = 1;
result = operand[i] + operand[i+1] =41;
operand[] = {36,41,41,-1,1728,123.43,123.43,6};
然后是减法 i = 2;
result = operand[i] - operand[i+1] =42;
operand[] = {36,41,42,42,1728,123.43,123.43,6};
接着还是加法 i = 3
result = operand[i] +operand[i+1] =42+1728=1770出错!!
可以看出一种按照这种逻辑运算还是会出错,分析一下如果乘法或者除法以及指数运算连在一起这他们所占用的位置都必须赋值为最后运算的值。因此一开始还必须对符号键分布进行判断处理。
更新数组
operand[] = {36,41,42,42,123.43,123.43,123.43,6};
result = result = operand[i] +operand[i+1] =42+124.3=166.3
并且这个赋值得覆盖所有有连着这些符号的区域‘’
即
operand[] = {36,41,42,166.3,166.3,166.3,166.3,6};
这在程序判断上可能有点棘手,但我觉得这是可以写清楚的。(就是连着两个符号的三个键值运算后的最终结果要一致,被其他运算符号使用了运算结果也要一致)
然后是最后一步加法 i = 6了
166.36+6=172.36
operand[] = {36,41,42,166.3,172.36,172.36,172.36,172.36};
最后运行等号最后一个 i = 7
rseult = operand[7] =172.36
则混和运算结束。
数码管显示172