我是嵌入式系统的讲师。我继承了一段运行良好的代码,但是由于缺少设计图,并且花了很多条件语句和标志,使我花了一些时间来理解。
该代码的目的是检测连接到微控制器端口的几个按钮之一何时被激活并记录事实。这些按钮为高电平有效,这意味着按下按钮时会在相应的引脚上产生高电压。开关弹跳的问题也已在固件中解决,因此同一引脚必须在预定的时间内保持高电平,然后才能被接受为有效引脚。
该代码每10毫秒调用一次,如果同一引脚为高电平,则计数器递增。当计数器达到预定义的值(在这种情况下为10)时,按钮按下被认为是有效的。因此,在这种情况下,在认为有效之前,引脚电压必须稳定在100mS的高电平。
为了更好地说明设计,并作为学生的状态机设计的另一个示例,我着手使用状态机设计方法重新设计系统。
状态机
状态机图如下图1所示。所述Button_PORT是定义为任何端口的按钮都连接到宏。这允许将按钮轻松移动到另一个端口。
#define Button_PORT PORTA
声明了一个联合,该联合将允许将按钮作为一个整体或单独进行访问。
typedef union { unsigned char Full; 结构{ 无符号字符B0:1; 未签名的字符B1:1; 未签名的字符B2:1; 未签名的字符B3:1; 未签名的字符B4:1; 未签名的字符B5:1; 未签名的字符B6:1; 未签名的字符B7:1; }; } Button_Type;
使用此类型定义了两个变量Button_Press和Temp_Press。Button_Press在反跳后保留按钮的最终值,而Temp_Press在反跳过程中保留按钮的中间值。
在应用程序代码中,设置了一个计时器,每10毫秒产生一个中断,然后评估状态机。状态机图将此时间表示为TICK事件(TICK_E)的发生。
有以下四种状态:
等待中:等待端口上的任何按钮被激活。
检测到:按钮已激活,因此进入此状态,并使用Temp_Press记录按钮的端口值。每10毫秒,将再次检查按钮端口,并且-在其值仍然相同的情况下-计数器将递增。用状态机的话来说,该变量称为“扩展状态变量”。
WaitForRelease:如果计数器达到预定义的最小值'MIN_BUTTON_COUNT',则Temp_press现在被视为有效,并且进入WaitForRelease状态以等待按钮释放,直到变量Button_Press保留了最终的按钮值。
更新:按钮已释放,因此最终值Button_Press已用去抖动的临时值'Temp_Press'更新。
图1.按钮反跳状态机(来源:Thomas Gartlan)
该状态机绘制在www.draw.io上,并根据Miro Samek的书《C / C ++中的实用UML状态图:嵌入式系统的事件驱动编程》中的内容使用表示法来表示状态。
从教学的角度来看,此状态机是状态,事件,警戒条件,Do操作,OnEntry操作和扩展状态变量的一个很好的示例。
正如我们前面提到的,有四个状态。唯一的事件是10mS TICK_E。从“等待”到“检测到”的过渡中,TICK_E上有一个保护状态,[Button_PORT> 0],在这种情况下,这意味着某些按钮已被激活。“已检测”状态下的“ OnEntry”操作会重置计数器,而“已检测”状态下的“执行”操作会在计数器中递增。计数器本身是扩展状态变量。
与原始的以条件标记为中心的代码相反,此状态机图提供了非常清晰的设计视图,因此为学生提供了一个很好的示例。
实施
该设计是使用MPLABX IDE和XC8编译器实现的。目标器件是Microchip的8位PIC18F4520微控制器。该设计以易于重复使用的方式实现。如前所述,端口是使用宏定义的,因此可以轻松地对其进行更改。而且,该代码打包在一个库中并发布到GitHub,这使得它可以轻松地维护和在任何项目中使用。
库头文件包含按钮结构和端口信息。库C文件包含状态机功能。代码中使用的名称与状态机图相匹配,从而更易于理解和调试设计。函数指针并不是真正需要的,也没有使用,因为它们会使学生在此阶段对设计的理解更加复杂。状态机功能的代码如下所示。
typedef枚举{Waiting,Detected,WaitForRelease,Update}状态; 无效Find_Button_Press(void) { 静态状态Button_State =正在等待; 静态无符号字符Button_Count = 0; 静态Button_Type Temp_Press; 开关(按钮状态){ 案例(等待): 如果(Button_PORT) { Button_State =已检测到; Button_Count = 0; Temp_Press.Full =按钮_端口; } 休息; 案例(检测): 如果(Temp_Press.Full == Button_PORT) { ++ Button_Count; 如果(Button_Count> MIN_BUTTON_COUNT) { Button_State = WaitForRelease; } } 别的 { Button_State =等待中; } 休息; 案例(WaitForRelease): 如果(!Button_PORT) { Button_State =更新; } 休息; 案例(更新): { Button_Press = Temp_Press; Button_State =等待中; Button_Count = 0; Temp_Press.Full = 0; } 休息; 默认: { Button_State =等待中; Button_Count = 0; Temp_Press.Full = 0; Button_Press.Full = 0; } } }
应用代码和测试
开发了一个简单的应用程序来说明和测试该设计。该应用程序测试代码的一部分如下所示。包含头文件,并定义了Button_Press变量。对头文件的唯一修改是定义用于按钮的端口。
/ ***************************************************** *** 包括图书馆 ****************************************************** *** / #include#include #include“ Buttons_Debounce.h” Button_Type Button_Press; //创建Button变量 / **************************************************** 功能原型 ****************************************************** / void Initial(void); void delay_s(unsigned char secs); / ***************************************************** 钟 ****************************************************** / #define _XTAL_FREQ 19660800 unsigned char count_test = 0; void __interrupt myIsr(void) { //定时器每10毫秒溢出一次 if(INTCONbits.TMR0IE && INTCONbits.TMR0IF){ Find_Button_Press(); //每10毫秒调用一次 WriteTimer0(40960); INTCONbits.TMR0IF = 0; //清除此中断条件 } } void main(无效) { Button_Press.Full = 0x00; 最初的(); 而(1) { if(Got_Button_E)//如果已按下某个按钮 { if(Button_Press.B0)//如果其按钮为0 PORTCbits.RC0 =〜PORTCbits.RC0; if(Button_Press.B1)//如果其按钮为0 PORTCbits.RC1 =〜PORTCbits.RC1; if(Button_Press.B2)//如果其按钮为0 PORTCbits.RC2 =〜PORTCbits.RC2; if(Button_Press.B3)//如果其按钮为0 PORTCbits.RC3 =〜PORTCbits.RC3; Button_Press.Full = 0x00; //清除所有按钮事件 } } }
将创建一个定时器中断,每10毫秒发生一次,并调用状态机功能。在此应用中,按钮连接到PORTB,而LED连接到PORTC。如果按下任何按钮,则相应的LED会切换而不会出现任何延迟或弹跳问题。处理按钮值后,将清除整个变量。
总体而言,该项目被认为是在设计阶段如何使用状态机方法,从而导致更清晰,更不易出错的实现的一个很好的例子。
编辑:hfy
-
微控制器
+关注
关注
48文章
7560浏览量
151500 -
嵌入式系统
+关注
关注
41文章
3593浏览量
129515 -
计数器
+关注
关注
32文章
2256浏览量
94629 -
状态机
+关注
关注
2文章
492浏览量
27552
发布评论请先 登录
相关推荐
评论