nullnull
第9章 51单片机实现状态机
9.1 有限状态机
有限状态机(FSM)与
流程
快递问题件怎么处理流程河南自建厂房流程下载关于规范招聘需求审批流程制作流程表下载邮件下载流程设计
图很相似,具有一组按照一定路径排列的状态,依据于状态中的事件和动作,一个状态可以转移到其他状态。
状态是时间中的一个点,例如,当你等火车的时候,你在等待状态。一种状态在一个状态机中,只能出现一次。
事件是某时发生的事情,例如火车到达,火车运行。
动作是当事件出现时,实现的任务,例如,火车到达后,上车
转移是两个状态之间的联系,可以从一个状态移动到另外一个状态。
状态图就是对一个事物在某个事件发生后从一个源状态到另外一个目的状态转移的图形描述。
状态图中,使用圆圈
表
关于同志近三年现实表现材料材料类招标技术评分表图表与交易pdf视力表打印pdf用图表说话 pdf
示状态,圆圈中的文字或数字表示该状态的名字或是编码,状态转移方向用箭头表示,在箭头旁写的文字是转移条件。对于梅里状态图,在箭头旁用“输入/输出”的
格式
pdf格式笔记格式下载页码格式下载公文格式下载简报格式下载
表示转移条件与满足该转移条件下的输出;而对于摩尔状态机,常将输出放在状态圆圈中。
null由图可知,当k=0时,状态从a0转移到a1,若是k0=1,从状态a1转移到a2,等等,若是reset=0,则无论在什么状态,都将转移到a0状态。 null一个状态机描述需要五个要素:状态、输入、输出、状态转移
函
关于工期滞后的函关于工程严重滞后的函关于工程进度滞后的回复函关于征求同志党风廉政意见的函关于征求廉洁自律情况的复函
数、输出函数。
如果在一个状态中包含着另一个状态序列,则称该状态为复合状态。
状态机就是能够根据状态转移条件进行状态转换和输出状态活动的自动机。状态机可以使用数字电路实现,或是单片机通过软件编程实现。
如果状态数量有限、输入数量有限、输出数量有限,则称为有限状态机(FSM)。
在数字电路中,有两类基本的电路,它们是组合电路和时序电路,它们之间的区别是组合电路的结构中没有反馈、输入值直接决定输出值,而时序电路的结构中具有反馈,其输出由状态和输入决定。状态机是时序电路的一种。null9.1.1 时钟同步有限状态机
如果一个时序电路中的触发器都使用同一个时钟信号,则称为同步状态机
1. 结构
时钟同步状态机的结构如图所示。
其中次态逻辑由现态和输入形成。状态记忆是n个触发器,用于记忆2n状态。输出逻辑由现在状态和输入形成。
在时钟的上升沿或是下降沿(取决于触发器结构),触发器在次态逻辑的控制下动作。 null2. 输出逻辑
如果一个时序电路的输出与现在状态和输入都有关,则称为梅里状态机,就是说,在某状态下,满足某输入条件的输出就是梅里输出。值得注意的是梅里状态机的输入变化直接引起输出的变化,而不是等到下一个状态到来时输出才变化。就是梅里状态机。
与输入无关,输出只与状态有关的状态机,称为摩尔状态机,所以摩尔状态机的输出与状态变化同步。摩尔状态机是使用广泛的状态机,如图所示。
null3. 状态编码
具有n个状态变量的状态机具有2n个状态。究竟一个状态用什么样的二进制数表示,就是状态编码问题。状态与它的编码之间不是唯一关系。最简单的状态编码就是采用自然二进制整数顺序来表示状态,这样的编码虽然简单,但是最终的电路未必是最简单的。如果要得到最简单的结果,最好把各种编码都试一试,但是太累人,一般情况下采用经验编码方法。
在状态编码时应该考虑的一些因素:
(1)选择初始状态的编码与状态机的复位状态相同,这样就会使状态机复位时就回到状态机的初始状态。
(2)每一次状态变化,应该使发生的状态变量最少。
(3)如果有未使用的状态,则尽量选择可以达到简化逻辑设计的状态编码。设计中应该保证从未使用状态一定可以进入初始状态。
null4. 主从状态机
一个大的状态机设计是非常复杂的,一般情况下,最好将大的状态机分解成小状态机的集合。一般划分的原则是按照功能划分,主要输入、输出和控制算法由主状态机完成,而辅助的、在主状态机控制下的算法由子状态机完成,就是说主状态机完成顶层算法,子状态机完成底层算法。
最常见的划分就是将计数器作为子状态机,这时,主状态机只要发出启动信号,而等待计数器返回的计数完成信号。虽然主状态机增加了启动计数器的输出信号和来自计数器的计数完成信号,但是该计数器就可以为主状态机节省n-1个状态。
主从状态机的一般结构如图所示。 null5. 状态转移条件
状态转移的条件和输入的信号数有关,n个输入信号,将有2n个转移条件,这些条件之间应该是互斥的,只能有一个转移条件有效。也就是只能转移到另外一个状态,而不是一个以上的状态。
在状态机设计过程中,对于状态转移的条件是容易给出的,但是使多个状态转移条件满足互斥条件确是比较难的,因为这需要更深刻的考虑状态、转移条件之间的关系。
一个状态只向另外一个状态转移的状态机是最简单的状态机,因为只有一个状态转移条件。
有些状态图只给出本状态向外状态的转移条件,而向自己转移的条件未给出,这种情况可以理解为只要不向外状态转移就保持在本状态不动。 null6. 状态机的输入信号
状态机的输入信号往往使是按键信号,既然是按键信号,则按下去的时间长短是随机的,可能是一个或几个时钟脉冲的时间,如果状态机的状态转移是在某输入信号的作用之下连续转移,例如,在状态1,当按键第一次为0时,转移到状态2,第二次为0时转移到状态1,则会因为按键一直保持在0,引起状态不断的转换,不能实现每按一次按键,转换一次状态的目的。
解决的方法是设计边沿微分电路,使按键按下时,只在下降沿输出一个时钟周期的低电平,保证只有一个状态转移。
7. 上电状态机初始化
在状态机上电时,无论为何种输入条件,都应该进入到一个确定的状态,该状态称为上电初始状态。有些状态机可以在上电时,自动进入上电初始状态,有些状态机则不能,需要复位信号的帮助才能进入上电初始状态。
9.1.2状态图
状态图是用于小型、中型状态机设计的一种方法,该方法的特点是简单。
状态图中的状态转移是用一根弧线表示的,所以不管有多少输入变量,也只能有一个转移条件表达式,应该使现在状态到所有次态的转移条件互斥,也就是说只能转移到一个次态。
另外需要说明的是用于控制器的状态机大部分都是摩尔状态机,就是说这些状态机的输出仅仅与状态有关。null[例题9-1] 设计一个顺序开关装置,该开关装置在按键k第一次按下时,三盏灯x、y和z同时点亮,当k 再次按下时,x灯立刻熄灭;y灯15s后熄灭,在y灯熄灭后18s后,z灯熄灭。nullnull[例题9-2] 试设计一个交通信号灯控制器,该交通灯的红、黄、绿灯亮灭顺序是如下: nullnull 9.2.1 C语言描述状态机的语句
int state=S0; //初始状态
while(1) //无限循环
{switch (state)
{case S0:
If( T0转移条件满足) {状态转移到满足T0条件的次态;操作;}
If( T1转移条件满足) {状态转移到满足T1条件的次态;操作;}
……
……
If( TN转移条件满足) {状态转移到满足TN条件的次态;操作;}
break;
case S1:
//插入S1的操作;
//插入离开S1的转移;
break;
……
case SN:
//插入SN的操作;
//插入离开SN的转移;
break;
}}
通常将表示状态转移的一系列通常将表示状态转移的多个if语句写成if-else语句,这样可以使转移条件形成互斥。null当然也可以直接使用if 语句描写状态机,例如:
if( state =S0)
{ if(T0转移条件满足)
{状态转移到满足T0条件的次态;操作;}
else
if(T1转移条件满足)
{ 状态转移到满足T1条件的次态;操作;}
else
……
……
if(TN转移条件满足)
{ 状态转移到满足TN条件的次态;操作;}
}
if( state =S1)
{//插入离开该状态的条件语句
//插入要转移的次态
//插入S1状态的操作语句
…}…
……
if( state =SN)
{//插入离开该状态的条件语句
//插入要转移的次态
//插入SN状态的操作语句 }
null9.2.2 描述状态机的完整C程序格式
#include
数码管的译码
表格
关于规范使用各类表格的通知入职表格免费下载关于主播时间做一个表格详细英语字母大小写表格下载简历表格模板下载
设置定时器的定时变量
设置状态变量
设置其他全局变量
主程序:
{ 局部变量初始化
定时器初始化语句
中断允许语句
主程序无限循环中:
{
(1)输入信号处理语句
获得所有的输入信号状态
null(2)状态机描述语句
主要是描述状态转移、转移条件、定时变量清零等
该状态下的输出语句
显示该状态的发光二极管(不是必须的,用于调试)
(3)显示处理语句
将定时数据送到数码管显示(不是必须的,用于调试)
(4)其他语句
(5)若是在状态描述语句中没有描述输出,可单独将各个状态下的输出描述
定时器中断服务程序中:
{设置静态局部变量
根据定时器工作模式确定是否设置初值,若是需要精确定时,选择不需要预置初值的工作方式2
每中断一次就增加数值的语句,获得确定的中断时间(例如1s)。
按照确定的中断时间,增加定时变量
}
null9.3 示例程序
在状态机描述中,除了直接描述状态机的语句外,还需要按键(钮)输入语句、定时器描述等语句,本节通过示例对按键、定时等的C描述给予介绍。
[示例程序1] 按键处理程序。
P3口全部连接按键,P0口连接的数码管显示按键的值,程序中变量kk是按键的值,不同的按键按下,数码管显示不同的值。
null源程序如下:
#include "AT89X51.h"
unsigned int code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned int kk=0x08;
void main()
{
unsigned int nn;
while(1)
{P3=0xff; //P3口连接8个按键作为输入
if((P3|0x00)!=0xff) //如果有按键按下
for(nn=0; nn<1000;nn++); //延时
if((P3|0x00)!=0xff) //按键还在按下状态
{ switch (P3)
{case 0xfe: //P3_0=0, fe=1111 1110
kk=0; break;
case 0xfd: //P3_1=0, fd=1111 1101
kk=1; break;
nullcase 0xfb: //P3_2=0, fb=1111 1011
kk=2; break;
case 0xf7: //P3_3=0 f7=1111 0111
kk=3; break;
case 0xef: //P3_4=0, ef=1110 1111
kk=4; break;
case 0xdf: //P3_5=0, df=1101 1111
kk=5; break;
case 0xbf: //P3_6=0, bf=1011 1111
kk=6; break;
case 0x7f: //P3_7=0 7f=0111 1111
kk=7; break;
default: kk=8;
}
while(P3!=0xff) ; //如果按键还在按下时,则停留在此,等待按键抬起
}
P0=table[kk]; //将键值送到P0口连接的数码管显示
}}
null[示例程序2] 按键控制秒计数(定时器0工作在方式1)。
P3口引脚连接按键,按键按下后,P0口连接的数码管显示按键号,P1口循环显示0~按键号之间的值,P2_0连接的LED灯亮1s,灭1s。如下程序采用定时器0,方式1工作,由于预装初值需要时间,所以不是精确计数。
null源程序如下:
#include "AT89X51.h" //文件夹名yuzuo2
unsigned int code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned int kk=0x08;
unsigned int t0;
void main()
{unsigned int nn;
TMOD=0x01; //定时器0,M1M0=01(模式1),gate=0(外中断不起作用),C/T=0
TH0=0x3c; //设置TH初值 0x3cb0=65536-50000=15536,50ms中断一次,每秒中断20次
TL0=0xb0; //设置TL初值
TR0=1; //启动定时器0
ET0=1; //设置中断使能寄存器,允许定时器0中断
EA=1; //允许所有中断源中断
while(1)
{P3= 0xff; //P3口连接8个按键作为输入
if(P3)!=0xff) //如果有键按下
for(nn=0; nn<1000;nn++); //延时消除抖动
if((P3)!=0xff) //如果按键抬起,执行如下语句。 null{ switch (P3)
{case 0xfe: //P3_0=0, fe=1111 1110
kk=0; break;
case 0xfd: //P3_1=0, fd=1111 1101
kk=1; break;
case 0xfb: //P3_2=0, fb=1111 1011
kk=2; break;
case 0xf7: //P3_3=0 f7=1111 0111
kk=3; break;
case 0xef: //P3_4=0, ef=1110 1111
kk=4; break;
case 0xdf: //P3_5=0, df=1101 1111
kk=5; break;
case 0xbf: //P3_6=0, bf=1011 1111
kk=6; break;
case 0x7f: //P3_7=0 7f=0111 1111
kk=7; break;
default: kk=8;
} nullwhile(P3!=0xff) ; //如果按键还在按下时,则停留在此,等待按键抬起
}
P0=table[kk]; //P0口连接的数码管显示按键值
P1=table[t0]; //P1口连接的数码管显示t0值
}}
void timer0() interrupt 1 //定时器0中断服务程序
{static unsigned int nn; //设置局部静态变量
TH0=0x3c; //重置初值
TL0=0xb0; //重置初值
nn++;
if (nn==20) //如果中断20次,执行如下语句
{nn=0;
t0++;
P2_0=!P2_0; //P2_0相连的LED灯闪烁
if(t0>kk) //由于kk是按键值,所以按键不同,kk不同,因此延迟时间也不同。
{t0=0;
}}}
null[示例程序3] 按键控制定时时间的程序(定时器0工作在方式2)。
按键值kk不同,则t0值的循环周期不同,因此可以通过kk改变t0,进而改变与t0相关的周期长度。 null源程序如下:
#include "AT89X51.h"
unsigned int code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned int kk=0x08;
unsigned int t0;
void main()
{unsigned int nn;
TMOD=0x02; //定时器0,工作模式M1,M0=2 (0000,0010),8位、自动重装定时模式
// gate=0(外中断不起作用),C/T=0(取时钟信号)
TH0=0x06; //预置初值6到定时器TH,预置250微秒溢出一次(12MHz)
//0.25ms,若是需要定时1s,则需要乘以4000,
TL0=0x06; //预置TL0值
TR0=1; //启动定时器0
ET0=1; //允许定时器中断
EA=1; //允许总中断
nullwhile(1)
{P3= 0xff; //P3口连接8个按键作为输入
if((P3)!=0xff) //有键按下
for(nn=0; nn<1000;nn++); //循环延时语句
if((P3)!=0xff) //如果按键处于按下状态
{ switch (P3)
{case 0xfe: //P3_0=0
kk=0; break;
case 0xfd: //P3_1=0
kk=1; break;
case 0xfb: //P3_2=0
kk=2; break;
case 0xf7: //P3_3=0
kk=3; break;
case 0xef: //P3_4=0
kk=4; break;
case 0xdf: //P3_5=0
kk=5; break;
case 0xbf: //P3_6=0
kk=6; break;
nullcase 0x7f: //P3_7=0
kk=7; break;
default: kk=8;
}
while(P3!=0xff) ; //如果按键还在按下时,则停留在此,等待按键抬起
}
P0=table[kk]; //P0口连接的数码管显示按键值
P1=table[t0]; //P1口连接的数码管显示t0值
}}
void timer0() interrupt 1 //定时器0中断服务程序
{ //中断后应产生时间标志,0.25ms中断一次(12MHz),
//延时1s,需要中断4000次},由于自动重装,所以定时精确
static unsigned int nn; //设置局部静态变量
nn++;
if (nn==4000) //如果中断4000次,相当于1s
{nn=0;
t0++;
P2_0=!P2_0; //使P2_0连接的LED灯闪烁
if(t0>kk) //kk改变t0的循环周期
{t0=0;
}}}null[示例程序4] 在使用如下的按键检测程序时,可以根据需要按键的多少裁减程序。如果需要按键按下时起作用,则应该去掉等待按键抬起的语句,但是应该注意,若是在状态转移条件中,连续两个转移条件具有相同的键值,这可能引起状态连续变化。P3口连接按键,P0口连接的数码管显示按键值。
null源程序如下:
#include "AT89X51.h"
unsigned int code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned int n0,n1,n2,n3,n4,n5,n6,n7;
unsigned int kk=8;
void main()
{
unsigned int nn;
while(1)
{
P3=0xff; //P3口连接8个按键作为输入
if(P3_0==0) //P3_0 键按下
{for(nn=0; nn<1000;nn++); //延时
if(P3_0==0) //如果还在按下状态,则n0=1,kk=0;否则n0=0,kk=8;
{n0=1;kk=0; P2_0=0;}
else {n0=0; }
while(!P3_0); //等待按键抬起
} nullif(P3_1==0) //P3_1连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_1==0) {n1=1;kk=1;}
else n1=0;
while(!P3_1);
}
if(P3_2==0) //P3_2连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_2==0) {n2=1;kk=2;}
else n2=0;
while(!P3_2);
}
if(P3_3==0) //P3_3连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_3==0) {n3=1;kk=3;} //P3_3连接的按键处理程序
else n3=0;
while(!P3_3);
}
if(P3_4==0)
{for(nn=0; nn<1000;nn++);
if(P3_4==0) {n4=1;kk=4;} //P3_4连接的按键处理程序
else n4=0;
while(!P3_4);
}
nullif(P3_5==0) //P3_5连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_5==0) {n5=1;kk=5;}
else n5=0;
while(!P3_5);
}
if(P3_6==0) //P3_6连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_6==0) {n6=1;kk=6;}
else n6=0;
while(!P3_6);
}
if(P3_7==0) //P3_7连接的按键处理程序
{for(nn=0; nn<1000;nn++);
if(P3_7==0) {n7=1;kk=7;}
else n7=0;
while(!P3_7);
}
P0=table[kk]; //P0口连接的数码管显示按键值
}}
null9.4 灯开关类例题
[例题9-3] 设计一个开关灯装置,该开关灯装置在按键k(P3_0引脚)第一次按下时,三盏灯a、b和c同时点亮,当k再次按下时,a灯立刻熄灭;b灯15s后熄灭,在b灯熄灭18s后,c灯熄灭
null源程序如下:
#include "AT89X51.h" //文件夹名yuzuo2
unsigned int code table[]={0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};
unsigned int kk=0x08,state=0;
unsigned int t0;
void main()
{unsigned int nn;
TMOD=0x01; //定时器0,M1,M0=01(模式1),gate=0(外中断不起作用),C/T=0
//单片机时钟12MHz
TH0=0x3c; //TH初值 0x3cb0=65536-50000=15536 50ms中断一次,每秒20次
TL0=0xb0; //设置TL初值
TR0=1; //启动定时器0
ET0=1; //设置中断使能寄存器,允许定时器0中断
EA=1; //允许所有中断
nullwhile(1)
{P3= 0xff; //P3口连接8个按键作为输入
if((P3)!=0xff) //如果有键按下
for(nn=0; nn<1000;nn++); //循环延时语句
if((P3)!=0xff) //如果按键抬起
{ switch (key)
{case 0xfe: //P3_0=0
kk=0; break;
case 0xfd: //P3_1=0
kk=1; break;
case 0xfb: //P3_2=0
kk=2; break;
case 0xf7: //P3_3=0
kk=3; break;
case 0xef: //P3_4=0
kk=4; break;
case 0xdf: //P3_5=0
kk=5; break;
case 0xbf: //P3_6=0
kk=6; break; nullcase 0x7f: //P3_7=0
kk=7; break;
default: kk=8;
}
while(P3!=0xff) ; //如果按键还在按下时,则停留在此,等待按键抬起
}
P1=table[t0/10]; //P1口连接的数码管显示t0 的高位
P0=table[t0%10]; //P0口连接的数码管显示t0 的低位
nullif((state==0)) //状态0
{P2_0=1;P2_1=1;P2_2=1; //三灯全灭,由于是LED灯,低电平灯亮
if(kk==0) //如果/P3_0引脚的按键按下
{state=1;kk=8;} //进入状态1,使kk=8,避免按键在状态1还在起作用
}
if((state==1)) //状态1
{P2_0=0;P2_1=0;P2_2=0; //三灯全亮
if(kk==0) //如果/P3_0引脚的按键按下
{state=2; kk=8;t0=0;} //进入状态2,使按键值kk=8,设置t0初值
}
if((state==2)) //状态2
{ P2_0=1;P2_1=0;P2_2=0; //a灯灭,b、c灯亮
if(t0>15) //如果定时变量t0值大于15
{state=3; t0=0;} //进入状态3,设置t0 初值
}
if((state==3)) //状态3
{ P2_0=1;P2_1=1;P2_2=0; //a、b灯灭,c灯亮
if(t0>18) //如果定时变量t0值大于15
{state=0; t0=0;} //进入状态0,t0变量置0
}}} nullvoid timer0() interrupt 1 //定时器0中断服务程序
{static unsigned int nn; //设置局部静态变量
TH0=0x3c; //重置初值
TL0=0xb0; //重置初值
nn++;
if (nn==20) //如果中断20次,相当于1s时间,执行如下语句
{nn=0;
t0++; //t0每1s时间间隔加1
if(t0>100)
{t0=0;
}}}