biying 发表于 2014-4-7 02:20:55

为什么这个状态机识别的if语句放到switch里就出错-出新问题

本帖最后由 biying 于 2014-4-8 01:03 编辑

这种一按开关就亮灯:
    while (1)
{                
               
             switch (status)

   {
         case 0:
               if (key_stime_ok)               
                             key_stime_ok = 0;        // 10ms到
                           if (read_key_n()==2)   //若长按2秒
                             PORTB ^= 1<<2;
                             break;         
       }
}
下面这种得长按2秒才亮灯
        while (1)
{                
                if (key_stime_ok)                               
                {
                        key_stime_ok = 0;                // 10ms到
                        if (read_key_n()==2)   //若长按2秒
                        {
                               PORTB ^= 1<<2;
                        }               
                       
                }
      
}
怎么同样的if语句,放到switch里面就不一样了?状态机都是一样的代码啊,请懂的朋友指点一下吧!谢谢!

biying 发表于 2014-4-7 02:25:12

看了几个小时了也找不到原因,没办法只有发上来请懂的朋友指点一下。我是用马潮老师的AVR学习板在练习,状态机代码也是教材里的,我只是想改成用switch语句来判断这个单键密码锁的程序,没想到一开始就卡在了长按开关这里

kyughanum 发表于 2014-4-7 02:53:33

第一个用switch判断status的状态,要status==0时才执行if语句,第二个则直接执行,你要看status的值是由什么设定的。
想不明白,就这么简单还要想几个钟头?

biying 发表于 2014-4-7 03:05:50

kyughanum 发表于 2014-4-7 02:53
第一个用switch判断status的状态,要status==0时才执行if语句,第二个则直接执行,你要看status的值是由什 ...

谢谢答复!之前我也试过了status==0也是一样的效果,我是想不通,必须状态机返回2(表示长按2秒了),灯才会有反应的,用了switch语句判断后,就只需短按就能让灯反应。你说状态机出问题了吧,可如果切换成用if来判断,就得长按灯才能有反应。到底这个switch语句是如何干扰状态机工作的?

biying 发表于 2014-4-7 03:09:29

我把源文件发上来,请大家帮我看一下吧!

biying 发表于 2014-4-7 03:19:08

就是用下面这段代码就正常,也就是长按2秒后灯才有反应

xiaobendan 发表于 2014-4-7 08:27:54

楼主,两个if结构是不一样的啊
你说的正常的第一个if后面有大括弧的,这样每次key_stime_ok==1时,read_key_n()只被执行一次,然后等待定时器再次把key_stime_ok变为1后再执行。就是每次间隔10ms,执行了200次后返回了结果2。
你说的不正常的第一个if后面没有大括弧,就是说他的作用只是当key_stime_ok==1时把他再变为0。后面的read_key_n()在每次while循环都被会被执行,同样是执行200次返回了2,但是间隔很快的,所以看起来是立即产生了输出。
所以还是得有仿真器,或者软件仿真看看程序到底怎么走的,所以我开始用的51,用keil就方便的很了。
你看明白了吗?

kyughanum 发表于 2014-4-7 08:41:20

biying 发表于 2014-4-7 03:05
谢谢答复!之前我也试过了status==0也是一样的效果,我是想不通,必须状态机返回2(表示长按2秒了),灯 ...

LZ,大概看了一下你的代码,switch (status) 这个中的status,只见在第13行的声明:
unsigned char simiao_counter,ermiao_counter,yimiao_counter,key_stime_counter,status;
并没有在其他地方有赋值,C语言里不是先赋值才使用的吗?这应该就是错误的原因吧?
并且,你这个状态,如果没有在其他地方有设置到在状态位,不懂你设置这个状态有什么用?

kyughanum 发表于 2014-4-7 08:45:43

xiaobendan 发表于 2014-4-7 08:27
楼主,两个if结构是不一样的啊
你说的正常的第一个if后面有大括弧的,这样每次key_stime_ok==1时,read_key ...

哈哈,是哦,现在才发现LZ的if (key_stime_ok)根本不一样

ijlc1314 发表于 2014-4-7 08:48:58


这种一按开关就亮灯:
    while (1)
{               
               
             switch (status)

   {
         case 0:
               if (key_stime_ok)
               {               
                               key_stime_ok = 0;      // 10ms到
                           if (read_key_n()==2)   //若长按2秒
                               PORTB ^= 1<<2;
                   }
                               break;         
       }

xiaobendan 发表于 2014-4-7 08:50:26

kyughanum 发表于 2014-4-7 08:41
LZ,大概看了一下你的代码,switch (status) 这个中的status,只见在第13行的声明:
unsigned char simia ...

哈哈,未赋值的变量默认是被清零的,楼主的这个问题和状态机没有关系,和switch没有关系。

toptrying 发表于 2014-4-7 10:08:21

switch(a)
{
      case 0:
             {

             }break;
      case 1:
             {

             }break;
      .
      .
      .
      default : break;
}

biying 发表于 2014-4-7 10:30:46

为什么这个状态机识别的if语句放到switch里就出错-已解决

谢谢大家的指点!

biying 发表于 2014-4-7 10:45:14

xiaobendan 发表于 2014-4-7 08:27
楼主,两个if结构是不一样的啊
你说的正常的第一个if后面有大括弧的,这样每次key_stime_ok==1时,read_key ...

谢谢指点!之前编译时提示大扩号出错,然后看第2版的书上例子里没有这个大括号,改来改去就成现在出错这种了。你说的运作流程我听懂了,也就是说那个10毫秒的清零(也就是按键消抖)没有和状态机联系起来,也许还没等到10毫秒的清零,状态机就被调用了200次,所以手感上像是一按就达到了应该长按2秒才出来的效果,对吧!谢谢你的指点!

biying 发表于 2014-4-7 10:47:04

kyughanum 发表于 2014-4-7 08:45
哈哈,是哦,现在才发现LZ的if (key_stime_ok)根本不一样

谢谢,就是没有加大括号了。再请教你一下,你说的status没有在其他地方有设置到在状态位是什么意思?

biying 发表于 2014-4-7 10:48:18

toptrying 发表于 2014-4-7 10:08
switch(a)
{
      case 0:


谢谢指点,就应该改成你写的这种!再次感谢!

biying 发表于 2014-4-7 11:02:29

toptrying 发表于 2014-4-7 10:08
switch(a)
{
      case 0:


谢谢你,我是看教材上case例子里没有这个大括号,所以就没加。不过这个不是问题的原因,通过大家的指点,我找到原因了,应该用(key_stime_ok)作为if的运行条件,只有当10毫秒到了后,才去执行一次read_key_n(),这样效果就对了,谢谢!

biying 发表于 2014-4-8 01:00:57

xiaobendan 发表于 2014-4-7 08:27
楼主,两个if结构是不一样的啊
你说的正常的第一个if后面有大括弧的,这样每次key_stime_ok==1时,read_key ...

这位老师,再请教你一下:        while (1)
        {                
                switch (status)
       {
         case 0:
            {
                if (key_stime_ok)               
                          {
                   key_stime_ok = 0;        // 10ms到
                             if (read_key_n()==2)   //若长按2秒
                                {
                     PORTB ^= 1<<2;
                     }
                   /*if (read_key_n()==1)   // 若短按
                                {
                     PORTB ^= 1<<1;
                     } */
                          }
               }
                   break;         
       }
    }
}我在上面的代码中加入短按的这段后,怎么长按、短按都没有反应了?我是想让长按、短按分别控制两个灯。

biying 发表于 2014-4-8 01:02:32

xiaobendan 发表于 2014-4-7 08:27
楼主,两个if结构是不一样的啊
你说的正常的第一个if后面有大括弧的,这样每次key_stime_ok==1时,read_key ...

换成下面这种switch语句就可以实现功能,怎么会这样?
switch (status)
       {
         case 0:
            {
                if (key_stime_ok)               
                          {
                   key_stime_ok = 0;        // 10ms到
                             switch(read_key_n())
                                {
                     case 1:
                           PORTB ^= 1<<2;
                           break;
                     case 2:
                            PORTB ^= 1<<1;
                            break;
                     }
                  
                          }
               }
                   break;         
       }
    }

biying 发表于 2014-4-8 01:10:11

switch (status)
       {
         case 0:
            {
                if (key_stime_ok)               
                            {
                   key_stime_ok = 0;      // 10ms到
                               if (read_key_n()==2)   //若长按2秒
                                  {
                     PORTB ^= 1<<2;
                     }
                   if (read_key_n()==1)   // 若短按
                                  {
                     PORTB ^= 1<<1;
                     }
                            }
               }
                   break;         
       }

就是改成这种后出错的,后面短按if判断应该是再次调用了一个状态机,可是应该没有区别吧?用switch的话应该是只调用了一个状态机,然后就来判断长按和断按,这种我理解了,就是用if来两次调用来判断那种还不理解

biying 发表于 2014-4-8 02:07:01

switch (status)
       {
         case 0:
            {
                if (key_stime_ok)               
                          {
                   key_stime_ok = 0;        // 10ms到
                             switch(read_key_n())
                                {
                     case 1:
                           PORTB ^= 1<<2;
                              ++mima;
                           break;
                     case 2:
                            PORTB ^= 1<<1;
                            break;
                     }
                  
                   if (++mima >=5)
                   {
                        PORTB ^= 1<<7;   
                   }
                          }
               }
                   break;
还有个问题,这里用的++变量,导致PB.7常亮加间隔快闪,这怎么理解?这个++mima >=5是我写多了两个+,可为什么一运行就长亮呢?

xiaobendan 发表于 2014-4-8 10:37:12

biying 发表于 2014-4-8 01:00
这位老师,再请教你一下:        while (1)
        {                
                switch (status)


今天比较忙
首先赞一下你的学习的精神
然后我得说一下,在论坛问问题,不要针对某个人,这样不好,某个人没时间,别人又不好插进来,你看,都这么长时间了,除了我以外没有人回复你了吧。
懂我的意思吗?
只要有时间,大家都会帮助你的,所以提问也要有技巧。有些问题也没有必要问,自己静下心来仔细想想就好,解决了,写到这里来,给和你同样求知的人一些帮助。

xiaobendan 发表于 2014-4-8 10:51:18

biying 发表于 2014-4-8 01:10
switch (status)
       {
         case 0:


连续调用两次,和一次的结果是哪里不一样?这样长按的时间就缩短了一倍,你看到的结果除了时间缩短,还有其他的变化吗?
你可以试试这样,再定义一个全局变量比如key_vaule,在if之前先取得read_key_n的值,然后在if里面使用key_vaule进行判断,这样不就是也调用一次了吗?然后看看结果

xiaobendan 发表于 2014-4-8 10:58:26

本帖最后由 xiaobendan 于 2014-4-8 11:01 编辑

biying 发表于 2014-4-8 02:07
switch (status)
       {
         case 0:


因为你在if的大括弧里面没有对这个变量清零啊,你看他小于5时,没有执行反转输出的语句,等到他符合条件后,一直在++,每次都符合,所以每次都会反转,知道这个变量溢出,unsigned char 是255,要是unsigned int,闪烁的时间会更长的,另外,20ms的闪烁你能看得到,眼力劲还真是不错啊,是根据亮度变化吗?
在反转的语句后加mima = 0;试试看是什么结果

xiaobendan 发表于 2014-4-8 15:08:46

明白了,原来你看到的闪是条件不成立时产生的,看起来很快,条件符合时常亮了,其实 不是常亮,是闪的太快了。

biying 发表于 2014-4-8 16:54:46

xiaobendan 发表于 2014-4-8 10:51
连续调用两次,和一次的结果是哪里不一样?这样长按的时间就缩短了一倍,你看到的结果除了时间缩短,还有 ...

我在阿莫轮坛发的问题,没想到都能这么快有回复,且还达到满意的指点,谢谢大家,也谢谢你的提醒,以后我会注意提问了。你说的对,先定义一个全局变量luru,当10ms到时,用这句uru=read_key_n()调用一次状态机,然后再用if来分成两种情况来判断(这样就只运行了一次状态机),就跟switch是一样的效果了。            
if (key_stime_ok)               
{
   key_stime_ok = 0;
   luru=read_key_n();         // 10ms到
   if (luru==2)   //若长按2秒
   {
         PORTB ^= 1<<2;
      }
      if (luru==1)   // 若短按
      {
          PORTB ^= 1<<1;
       }               
}

biying 发表于 2014-4-8 17:00:26

本帖最后由 biying 于 2014-4-8 17:21 编辑

你这么一解释,终于让我明白为什么有的代码里要有类似的这种luru=read_key_n()句子,原来目的就是只想运行一次read_key_n()这种子函数,来达到后面的判断。调用两次将浪费CPU时间,同时可能导致相应模块出错。

biying 发表于 2014-4-8 17:18:32

下面这句我测试过好多次,短按和长按后,灯都没有反应,你的分析我理解了,每个10毫秒就调用了两次状态机,将导致2秒的长按变成按1秒就算长按了,因为2秒的长按计时是做到了状态机里,通过计数被调用200次,每10m调用一次,算下来就是2秒。通过分析应该长按一秒后灯应该有变化,短按另一颗灯也应该有变化才对啊,可为什么下载后灯都不变化呢?if (key_stime_ok)               
                   {
                      key_stime_ok = 0;   // 10ms到
                        if (read_key_n()==2)   //若长按2秒
                        {
                              PORTB ^= 1<<2;
                        }
                        if (read_key_n()==1)   // 若短按
                        {
                              PORTB ^= 1<<1;
                        }                  
                   }

biying 发表于 2014-4-8 17:51:03

下面这句你说的我理解了,原来程序是这样运行的:上电后如果不按键,if (++mima >=5)这句话会每隔10ms执行一次,导致mima 不停的加,直接到溢出后再继续加。如果把这个5次改成100次,就会看到灯是亮一秒灭秒的运行的,符合100X10ms=1秒的分析。原来程序是这样运行的啊!if (key_stime_ok)               
{
    key_stime_ok = 0;      // 10ms到
       switch(read_key_n())
      {
          case 1:
               PORTB ^= 1<<2;
                  ++mima;
                  break;
         case 2:
                  PORTB ^= 1<<1;
               break;
      }
if (++mima >=5)//这里的两个++被我写重了,本来这句应该只是判断上面的短按是否达到5次的。
      {
         PORTB ^= 1<<7;   
         }
}

biying 发表于 2014-4-8 18:19:34

我从前几天初学单片机到现在,预计先设计一个用8脚tiny13A制作的单键密码锁,只有一个按钮作为人机交互,通过它可自行设定密码位数和密码,带看门狗和掉电模式(低功耗),到今天为止,学习进度已经达到了识别一位密码的能力,现在的效果是按5次(表示输入密码5)“开门”,长按2秒“关门”。按错如何处理还没有做。

xiaobendan 发表于 2014-4-8 19:53:56

biying 发表于 2014-4-8 17:18
下面这句我测试过好多次,短按和长按后,灯都没有反应,你的分析我理解了,每个10毫秒就调用了两次状态机, ...

这个要更仔细的观察了
首先说==2的问题,你看看每次10MS完成后都调用两次read_key_n(),那么完成200次的时候,就是返回2的时候,正好是你判断==1的时候,结果很明显,你明白了吗?
然后说==1的问题,在read_key_n()中只要key_time<200之前松开按键,程序都会返回1,但是这个是10MS才执行一次,而你判断==1的时候是在判断==2之后,假设你在10ms期间松开的,那么在==2这次调用的时候返回了1,结果很明显,而在紧接着==1的调用的时候,返回的是0了,所以就……
那么就只有在程序执行了==2的调用之后,在==1的调用之前的这点时间里面,你松开按键,才会有可能返回1,问题是这段时间相对于10MS来说,比例有多少,你自己也清楚吧。
为了证明一下,你可以把两个if的前后顺序调换一下试试看会不会有不同的结果。

biying 发表于 2014-4-10 10:06:17

xiaobendan 发表于 2014-4-8 19:53
这个要更仔细的观察了
首先说==2的问题,你看看每次10MS完成后都调用两次read_key_n(),那么完成200次的 ...

if (key_stime_ok)               
                   {
                      key_stime_ok = 0;   // 10ms到
                        if (read_key_n()==2)   //若长按2秒
                        {
                              PORTB ^= 1<<2;
                        }
                        if (read_key_n()==1)   // 若短按
                        {
                              PORTB ^= 1<<1;
                        }                  
                   }
谢谢!按你的分析,我又对状态机有了新的认识。为了更深刻的了解状态机,我正在对照汇编码,了解此程序中单片机每个机器时钟对代码的运行方式。C语言都才初学,现在又要初学汇编,真的太难了,不过如果不学汇编,我还真看不清C语言是如何执行的。就拿状态机中switch语句来说,在执行key_state_3这句话之前,我还以为到了switch语句后就直接跳到key_state_3这句了,可实际仿真的结果是,到switch语句后,是这样走的:key_state_0,key_state_1,key_state_2,然后才走到key_state_3。再如i运行到f (key_press)时,它是要再读一次(key_press)状态呢,还是取在程序调用read_key_n(),运行初始化读按键I/O电平时的呢?之前没搞清楚,现在仿真后明白了,原来状态机中switch语句后的那些if判断(key_press)状态的,都是取初始化读按键I/O电平时的那个值。如果能看懂汇编,也许我就能早明白这个过程了。

xiaobendan 发表于 2014-4-10 13:25:30

不用吧,AVR我也没有学过汇编的。关键是要细心,不能浮躁,更不能想当然,凡事都有个理字。
51的汇编,现在都看不懂了。

biying 发表于 2014-4-10 13:52:41

xiaobendan 发表于 2014-4-10 13:25
不用吧,AVR我也没有学过汇编的。关键是要细心,不能浮躁,更不能想当然,凡事都有个理字。
51的汇编,现在 ...

谢谢!第二版书上将汇编的也不适合我这种初学的,太难看懂了,结合JTAG,我也才看懂@0000006B: read_key_n
---- demo_9_2.c -----------------------------------------------------------------------------------
53:               key_press = key_input;                                // 读按键I/O电平
+0000006B:   931A      ST      -Y,R17         Store indirect and predecrement
+0000006C:   930A      ST      -Y,R16         Store indirect and predecrement
+0000006D:   E000      LDI       R16,0x00       Load immediate
+0000006E:   E0E0      LDI       R30,0x00       Load immediate
+0000006F:   9987      SBIC      0x10,7         Skip if bit in I/O register cleared
+00000070:   E0E1      LDI       R30,0x01       Load immediate
+00000071:   2F1E      MOV       R17,R30      Copy register
等 SBIC那行过了后,再放开按键,后面的if,就会把key_press的值认做0了。像我这种初学单片机,不懂汇编,还真得用JTAG配合原汇编代码来一条条代码的看,再用心细细想,才可能明白

biying 发表于 2014-4-11 07:52:09

奇怪,为什么今天早上我来仿真调试的时候出现新程序下载不到板上,程序没有提示出错,可实际运行的程序是之前下载的,点了一通后之前下载的程序倒是没有运行了,可新程序还是没有真正下载到板上,而且我发现运行仿真的时候和以前不一样了,光标箭头跑到其它地方了,之前开始应该是在main的下面呢,看图怎么会停在break那里呢?

biying 发表于 2014-4-11 10:36:24

这个问题我转到另一个帖子了
初学单片机-单键密码锁制作日志
http://www.amobbs.com/thread-5576508-1-1.html
(出处: amoBBS 阿莫电子论坛)
页: [1]
查看完整版本: 为什么这个状态机识别的if语句放到switch里就出错-出新问题