大家好,欢迎来到IT知识分享网。
目录
在不同状态下,接触到不同的按键事件,会发生相应的状态转换和动作,详细分解如下:
计算机(状态机编程)
简单计算器应用
1、项目概述
本项目的目的是开发一款基于51单片机的计算器,实现简单的正整数四则运算功能。按键布局如下:
每次按键动作要有1次蜂鸣器的短促提示音。
2、功能需求
该计算器可以实现简单的四则运算功能。分为以下几步:
输入运算数1
初始显示0
通过按键逐位输入运算数1,高位先行。数码管显示输入的运算数1。输入任一运算符后即进入下阶段,锁定了运算数1。
此阶段L0亮起
输入运算符
通过按键逐位输入运算符(加减乘除),可覆盖更改,加减乘除分别点亮L4~7作为指示。数码管显示上阶段输入的运算数1。输入运算符后,按下任意数字按键即进入下阶段,锁定运算符。
此阶段L1亮起
输入运算数2
通过数字按键逐位输入运算数2,高位先行。数码管显示输入的运算数2。
此阶段L2亮起。
按下等号,显示计算结果
根据输入的运算数1、运算符、运算数2完成计算,结果显示在数码管上。
此阶段L3亮起。
归零
在任意状态下,按下AC键将清空所有输入,回到初始输入运算数1状态。
数码管显示要求
运算数和计算结果显示。
其他使用细节效果参看Windows的标准计算器
3、扩展要求
实现正负小数的四则运算。即运算数可输入负数、小数。
运算数和计算结果右对齐显示。
解题分析
在这里我们可以使用**按键驱动的状态机来抽象这个简单计算器的运行过程。
状态机是一种编程模式,它通过定义不同的状态和状态之间的转换来管理程序的行为。在这个计算器项目中,状态机用于处理用户通过矩阵键盘输入的数字和运算符,并根据输入更新计算器的状态和显示结果。
4.1 状态机编程思想在简单计算器实现的应用
状态:
-
- 系统在任何给定时间点只能处于一个状态。
状态是系统在某一时刻的特定行为模式或条件。
对于简单计算器,我们可以定义四个状态:
-
- State 1:输入运算符。
- State 2:输入运算数2。
- State 3:显示计算结果。
State 0:输入运算数1。
事件(按键处理):
- 事件可以是用户的输入、传感器的读数、定时器的触发等。
事件是触发状态转换的外部或内部信号。
对于简单计算器,其状态转换显然是由使用这的按键输入来驱动的
-
- 对于计算器,我们可以将其输入分为四类:
- 数字键:0~9
- 运算符:加减乘除
- 归零:
- 等于:
我们可以使用 Key_Value 变量来存储当前按键的值。当期为有效键值时,驱动执行状态机。
转换:
- 转换可以伴随有动作(Action),即在状态转换时执行的代码。
- 前述四种状态下,显然不同按键会触发不同的状态转换,并伴随产生不同的动作。
转换是状态之间的变化,由特定事件触发。
动作:
- 动作可以是输出信息、更新变量、调用函数等。
- 在每个状态下,根据按键值执行不同的操作,如更新数字、更新运算符、执行计算等。
动作是在状态转换时执行的操作。
4.2 计算器状态机转换图
基于简单计算器运行逻辑,我们可把它分为四个状态:
- State 1:输入运算符。
- State 2:输入运算数2。
- State 3:显示计算结果。
State 0:输入运算数1。
在不同状态下,接触到不同的按键事件,会发生相应的状态转换和动作,详细分解如下:
- *状态 0*:输入运算数1
- 输入数字:更新
Calc_Num1
。 - 输入运算符:存储运算符并进入
State 1
。 - 输入
C
:重置所有变量并回到State 0
。
- 输入数字:更新
- *状态 1*:输入运算符
- 输入数字:进入
State 2
并初始化Calc_Num2
。 - 输入运算符:更新运算符。
- 输入
C
:重置所有变量并回到State 0
。
- 输入数字:进入
- *状态 2*:输入运算数2
- 输入数字:更新
Calc_Num2
。 - 输入运算符:执行计算,更新
Calc_Num1
为结果,存储新的运算符并回到State 1
。 - 输入
C
:重置所有变量并回到State 0
。 - 输入
=
:执行计算并进入State 3
。
- 输入数字:更新
- *状态 3*:显示计算结果
- 输入数字:重置计算器并进入
State 0
。 - 输入运算符:更新
Calc_Num1
为结果,存储新的运算符并进入State 1
。 - 输入
C
:重置所有变量并回到State 0
。 - 输入
=
:重新计算并保持在State 3
。
- 输入数字:重置计算器并进入
计算器的状态转换图
状态转换图(State Transition Diagram,简称STD)是一种图形化表示状态机运行过程的工具。它通过图形符号直观地展示系统的状态、状态之间的转换以及触发这些转换的事件和动作。状态转换图是理解和设计状态机逻辑的重要工具,广泛应用于软件工程、硬件设计、控制系统等领域。
这是一个基于按键驱动的状态机,根据不同状态下,按键动作的响应,我们将按键分为四类:
数字键:代表0,1,2,3,4,5,6,7,8,9数字输入
符号键:代表+,- ,*,/ 符号输入
清零键:C
等于键:=
状态转换表
不同状态下,不同分类的按键动作带来的相应可以细化成下表:
*输入* | *输出* | ||
---|---|---|---|
*所处状态* | *检测到的**按键类型* | *发生的**状态转换* | *产生的动作* |
*State 0:**输入运算数1* | *数字键* | State 0 | 更新运算数1,之前的值左移1位,新输入的数字放在个位 |
*符号键* | *State 1* | 确认运算数1,存储运算符 | |
*清零键* | State 0 | 运算数,运算符,结果都清零 | |
*等于键* | State 0 | 无效操作 | |
*State 1:**输入运算符* | *数字键* | *State 2* | 确认运算符,记录运算数2的首位数字 |
*符号键* | State 1 | 更新运算符 | |
*清零键* | *State 0* | 运算数,运算符,结果都清零 | |
*等于键* | *State 0* | 无效操作 | |
*State 2:**输入运算数2* | *数字键* | State 2 | 更新运算数2,之前的值左移1位,新输入的数字放在个位 |
*符号键* | *State 1* | 先进行一次运算,将结果作为下一次计算的运算数1,记录刚运算符为下一次运算的运算符 | |
*清零键* | *State 0* | 运算数,运算符,结果都清零 | |
*等于键* | *State 3* | 进行运算,结果存入Result | |
*State 3:**显示计算结果* | *数字键* | *State 0* | 重新开始一次运算,记录运算数1的首位数字 |
*符号键* | *State 1* | 将结果作为运算数1,记录运算符 | |
*清零键* | *State 0* | 运算数,运算符,结果都清零 | |
*等于键* | State 3 | 将结果作为运算数1,保留之前的运算符和运算数2,迭代计算 |
5、编程实现
1>显示任务
/* 函数名程: void Display_Task(void) 函数功能: 显示内容执行任务,0.1s刷新一次显示内容 参数列表: 返回值 : */ void Display_Task(void) { static uint Display_Tick; if((Tick - Display_Tick) > 100) { switch (Page) { case 0://输入显示运算数1 sprintf(Dsp_Bit, "%8.7g", (float)Keynum1); LED0 = 1; LED1 = 0; LED2 = 0; LED3 = 0; break; case 1://输入运算符号 sprintf(Dsp_Bit, "%8.7g", (float)Keynum1); switch (Operate) { case '+': LED4=1;LED5=0;LED6=0;LED7=0; break; case '-': LED4=0;LED5=1;LED6=0;LED7=0; break; case '*': LED4=0;LED5=0;LED6=1;LED7=0; break; case '/': LED4=0;LED5=0;LED6=0;LED7=1; break; default: break; } break; case 2://输入显示运算数2 sprintf(Dsp_Bit, "%8.7g", (float)Keynum2); LED0 = 0; LED1 = 0; LED2 = 1; LED3 = 0; break; case 3://显示计算结果 sprintf(Dsp_Bit, "%8.7g", (float)Result); LED0 = 1; LED1 = 1; LED2 = 1; LED3 = 1;LED4=1;LED5=1;LED6=1;LED7=1; break; default: break; } } Seg_Tran(); }
2>计算任务
/* 函数名程: void Calculate(void) 函数功能: 计算 参数列表: 返回值 : 无 */ void Calculate(void) { switch (Operate) { case '+': Result=Keynum1+Keynum2; break; case '-': Result=Keynum1-Keynum2; break; case '*': Result=Keynum1*Keynum2; break; case '/': Result=Keynum1/Keynum2; break; default: break; } }
3>计算逻辑任务
/* 函数名程: void Calculate_Task (void) 函数功能: 实现计算功能 参数列表: 返回值 : 无 */ void Calculate_Task (void) { if(Key_Value !='N') { Button_Flag = 1; switch (Page)//界面 { case 0://输入运算数1 switch (Key_Value) { case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9://按下数字按键 Keynum1=Keynum1*10+Key_Value; break; //运算符号按键 case '+':case '-':case '*':case '/': Operate=Key_Value; Page=1; break; //清除 case 'C': Page=0; Keynum1=0; Keynum2=0; Result=0; Operate=0; break; //等于号无效 default: break; } break; case 1://输入运算符 switch (Key_Value) { case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9://如果是输入数字按键,则进入下一阶段 Keynum2=Keynum2*10+Key_Value; Page=2; break; case '+':case '-':case '*':case '/'://如果输入运算符号,则覆盖修改 Operate=Key_Value; break; //清除 case 'C': Page=0; Keynum1=0; Keynum2=0; Result=0; Operate=0; break; //等于号无效 case '=': break; default: break; } break; case 2://输入运算数2 switch (Key_Value) { case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9://更新数字 Keynum2=Keynum2*10+Key_Value; break; case '+':case '-':case '*':case '/'://执行计算,更新为num1的结果,存储新的运算符号,并回到Page1 Calculate(); Page = 1; Keynum1 = Result;//储存输入的运算符 Operate = Key_Value;//覆盖储存输入的运算符 break; case 'C': //清除 Page=0; Keynum1=0; Keynum2=0; Result=0; Operate=0; break; case '=': Calculate(); Page=3; break; default: break; } break; case 3://显示计算结果 switch (Key_Value) { case 0:case 1:case 2:case 3:case 4:case 5:case 6:case 7:case 8:case 9://输入数字,重置计算机 Keynum1=Key_Value; Page=0; break; case '+':case '-':case '*':case '/'://更新num1的结果,存储新的运算符并进入Page1 Keynum1 = Result;//储存输入的运算符 Operate = Key_Value;//覆盖储存输入的运算符 Page=1 ; break; case 'C': //清除 Page=0; Keynum1=0; Keynum2=0; Result=0; Operate=0; break; case '=': Keynum1 = Result; Calculate(); Page=3; break; default: break; } break; default: break; } } Key_Value='N'; }
4>按键任务
/* 函数名程: void Key_Task(void) 函数功能: 按键执行任务,每2ms执行一次 参数列表: 返回值 : */ void Key_Task(void) { static uint Key_Tick; if((Tick - Key_Tick) > 2) { Key_Tick = Tick; Key_Value = KeyTrans(MatKeyScan_4x4()); } }
5>按键转化任务
/* 函数名程: void Key_Task(void) 函数功能: 按键执行任务,每2ms执行一次 参数列表: 返回值 : */ void Key_Task(void) { static uint Key_Tick; if((Tick - Key_Tick) > 2) { Key_Tick = Tick; Key_Value = KeyTrans(MatKeyScan_4x4()); } } /* 函数名程: uchar KeyTrans(uchar keynum) 函数功能: 按键返回相应的值 参数列表: 返回值 : Key_Value */ uchar KeyTrans(uchar keynum) { uchar KeyValue; switch (keynum) { //数字区域 case 8: KeyValue= 0; break; case 5: KeyValue= 1; break; case 9: KeyValue= 2; break; case 13: KeyValue= 3; break; case 6: KeyValue= 4; break; case 10: KeyValue= 5; break; case 14: KeyValue= 6; break; case 7: KeyValue= 7; break; case 11: KeyValue= 8; break; case 15: KeyValue= 9; break; //运算符号 case 19: KeyValue= '/' ; break; case 18: KeyValue= '*' ; break; case 17: KeyValue= '-' ; break; case 16: KeyValue= '+' ; break; case 12: KeyValue= '=' ; break; //清除符号 case 4: KeyValue= 'C' ; break; default:KeyValue = 'N'; break; } return KeyValue; }
6>4X4矩阵按键扫描按键扫描
/* 函数名程: uchar MatKeyScan_4x4(void) 函数功能: 检测矩阵按键是否按下,按下则返回对应键值 返回值 : S4~S19 */ uchar MatKeyScan_4x4(void) { static uchar pre_scan = 0, //continue value pre_trg = 0; //last trigger value uchar scan = 0, //trigger value trg = 0, //current value value = 0, //必须初始化为3 key_x = 0, key_y = 0; P3 = 0x0f; P4 = 0x00; if(!P30) key_x = 3; //获取X轴坐标 else if(!P31) key_x = 2; else if(!P32) key_x = 1; else if(!P33) key_x = 0; P3 = 0xf0; P4 = 0xff; if(!P34) key_y = 4; //获取Y轴坐标 else if(!P35) key_y = 3; else if(!P42) key_y = 2; else if(!P44) key_y = 1; scan = key_x + key_y * 4; //根据按键连线和键值设置得出的键值计算式 if(scan == pre_scan) //消抖:只有连续两次检测相同才触发,否则无效 trg = scan; else trg = 0; if(trg == pre_trg) //自锁:连续相同的触发,不再产生键值(0) value = 0; else value = trg; //前后不同的触发,更新成后一次的键值。如果是按下S15,即前一次触发0,后一次触发15,更新15 pre_scan = scan; //每次检测后,保存值至PRE里以便下次比较 pre_trg = trg; return value; //返回独立按键的键值 } #define LONG_PRESS_THRESHOLD 100 // 长按阈值设定,单位为10ms uchar MatKeyScan_2x4(void) { static uchar scanState = 0; // 扫描状态 static uchar pressDuration = 0; // 按下持续时间计数器 static uchar keyValue = 0; // 按键值局部变量 // 设置列线为高电平 P44 = 1; P42 = 1; P34 = 1; P35 = 1; // 设置行线为高电平 P33 = 0; P32 = 0; switch (scanState) { case 0: // 准备扫描 if (P44 == 0 || P42 == 0 || P34 == 0 || P35 == 0) { // 检查是否有键被按下 scanState = 1; } break; case 1: // 确定按键按下 if (P44 == 0) { // 检查第一列 keyValue = 4; } else if (P42 == 0) { // 检查第二列 keyValue = 8; } else if (P35== 0) { // 检查第一列 keyValue = 12; } else if (P34 == 0) { // 检查第二列 keyValue = 16; } else { // 如果两行都不是低电平,说明之前的检测可能是干扰 scanState = 0; break; } // 设置列线为低电平 P44 = 0; P42 = 0; P34 = 0; P35 = 0; // 设置行线为高电平 P33 = 1; P32 = 1; if (keyValue == 4) { // 按键在第一列 if (P33 == 0) keyValue = 4; // S4 if (P32 == 0) keyValue = 5; // S5 } else if (keyValue == 8) { // 按键在第二列 if (P33 == 0) keyValue = 8; // S8 if (P32 == 0) keyValue = 9; // S9 }else if (keyValue == 12) { // 按键在第三列 if (P33 == 0) keyValue = 12; // S12 if (P32 == 0) keyValue = 13; // S13 }else if (keyValue == 16) { // 按键在第四列 if (P33 == 0) keyValue = 16; // S16 if (P32 == 0) keyValue = 17; // S17 } scanState = 2; pressDuration = 0; // 开始计时 break; case 2: // 按键释放,检测长按 if (P44 && P42 & P34 & P35) { // // 按键释放 // if (pressDuration == LONG_PRESS_THRESHOLD) { // 长按了 // keyValue = keyValue | 0x80; // 输出长按键值 // } else { // 不是长按 键值不变 // } // pressDuration = 0; // 计时清零 scanState = 0; // 重置状态 return keyValue; // 返回短按键值 // } else { // // 按键持续被按下 // if (pressDuration < LONG_PRESS_THRESHOLD) { // pressDuration++; // 更新持续时间 // } } break; } return 0; // 无按键操作 }
7>按键提示音
/* 函数名程: void Logic_Task(void) 函数功能: 定时按一定逻辑执行任务 参数列表: 返回值 : */ void Logic_Task(void) { static uint Logic_Tick,Button_Tick; if((Tick - Logic_Tick) > 500) { Logic_Tick = Tick; } //按键音任务 if((Tick - Button_Tick) > 100) { Button_Tick = Tick; if(Button_Flag) { if(Button_Flag == 1) { Buzzer = 1; Button_Flag++; } else { Buzzer = 0; Button_Flag = 0; } } } }
6、效果图
例如:通过按下S5从1开始按下S17减号,按下S9减2,之后按下S12等于号,可以逐次递减。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128593.html