搜索
bottom↓
回复: 36

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

[复制链接]

出0入0汤圆

发表于 2014-4-7 02:20:55 | 显示全部楼层 |阅读模式
本帖最后由 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里面就不一样了?状态机都是一样的代码啊,请懂的朋友指点一下吧!谢谢!

出0入0汤圆

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

出0入0汤圆

发表于 2014-4-7 02:53:33 | 显示全部楼层
第一个用switch判断status的状态,要status==0时才执行if语句,第二个则直接执行,你要看status的值是由什么设定的。
想不明白,就这么简单还要想几个钟头?

出0入0汤圆

 楼主| 发表于 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语句是如何干扰状态机工作的?

出0入0汤圆

 楼主| 发表于 2014-4-7 03:09:29 | 显示全部楼层
我把源文件发上来,请大家帮我看一下吧!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-7 03:19:08 | 显示全部楼层
就是用下面这段代码就正常,也就是长按2秒后灯才有反应

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 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就方便的很了。
你看明白了吗?

出0入0汤圆

发表于 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语言里不是先赋值才使用的吗?这应该就是错误的原因吧?
并且,你这个状态,如果没有在其他地方有设置到在状态位,不懂你设置这个状态有什么用?

出0入0汤圆

发表于 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)根本不一样

出0入0汤圆

发表于 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;         
       }

出0入0汤圆

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

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

出1000入0汤圆

发表于 2014-4-7 10:08:21 | 显示全部楼层
switch(a)
{
      case 0:
             {

             }break;
      case 1:
             {

             }break;
      .
      .
      .
      default : break;
}

出0入0汤圆

 楼主| 发表于 2014-4-7 10:30:46 | 显示全部楼层
谢谢大家的指点!

出0入0汤圆

 楼主| 发表于 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秒才出来的效果,对吧!谢谢你的指点!

出0入0汤圆

 楼主| 发表于 2014-4-7 10:47:04 | 显示全部楼层
kyughanum 发表于 2014-4-7 08:45
哈哈,是哦,现在才发现LZ的if (key_stime_ok)根本不一样

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

出0入0汤圆

 楼主| 发表于 2014-4-7 10:48:18 | 显示全部楼层
toptrying 发表于 2014-4-7 10:08
switch(a)
{
      case 0:

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

出0入0汤圆

 楼主| 发表于 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(),这样效果就对了,谢谢!

出0入0汤圆

 楼主| 发表于 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;         
       }
    }
}我在上面的代码中加入短按的这段后,怎么长按、短按都没有反应了?我是想让长按、短按分别控制两个灯。

出0入0汤圆

 楼主| 发表于 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;         
       }
    }

出0入0汤圆

 楼主| 发表于 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来两次调用来判断那种还不理解

出0入0汤圆

 楼主| 发表于 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[0];
                             break;
                       case 2:
                            PORTB ^= 1<<1;
                            break;
                       }
                  
                   if (++mima[0] >=5)
                   {
                        PORTB ^= 1<<7;   
                   }
                            }
               }
                   break;  
还有个问题,这里用的++变量,导致PB.7常亮加间隔快闪,这怎么理解?这个++mima[0] >=5是我写多了两个+,可为什么一运行就长亮呢?

出0入0汤圆

发表于 2014-4-8 10:37:12 | 显示全部楼层
biying 发表于 2014-4-8 01:00
这位老师,再请教你一下:        while (1)
        {                
                switch (status)

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

出0入0汤圆

发表于 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进行判断,这样不就是也调用一次了吗?然后看看结果

出0入0汤圆

发表于 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] = 0;试试看是什么结果

出0入0汤圆

发表于 2014-4-8 15:08:46 | 显示全部楼层
明白了,原来你看到的闪是条件不成立时产生的,看起来很快,条件符合时常亮了,其实 不是常亮,是闪的太快了。

出0入0汤圆

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

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

出0入0汤圆

 楼主| 发表于 2014-4-8 17:00:26 | 显示全部楼层
本帖最后由 biying 于 2014-4-8 17:21 编辑

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

出0入0汤圆

 楼主| 发表于 2014-4-8 17:18:32 | 显示全部楼层
下面这句我测试过好多次,短按和长按后,灯都没有反应,你的分析我理解了,每个10毫秒就调用了两次状态机,将导致2秒的长按变成按1秒就算长按了,因为2秒的长按计时是做到了状态机里,通过计数被调用200次,每10m调用一次,算下来就是2秒。通过分析应该长按一秒后灯应该有变化,短按另一颗灯也应该有变化才对啊,可为什么下载后灯都不变化呢?
  1. if (key_stime_ok)               
  2.                    {
  3.                       key_stime_ok = 0;   // 10ms到
  4.                           if (read_key_n()==2)   //若长按2秒
  5.                           {
  6.                               PORTB ^= 1<<2;
  7.                           }
  8.                           if (read_key_n()==1)   // 若短按
  9.                           {
  10.                               PORTB ^= 1<<1;
  11.                           }                  
  12.                    }
复制代码

出0入0汤圆

 楼主| 发表于 2014-4-8 17:51:03 | 显示全部楼层
下面这句你说的我理解了,原来程序是这样运行的:上电后如果不按键,if (++mima[0] >=5)这句话会每隔10ms执行一次,导致mima[0] 不停的加,直接到溢出后再继续加。如果把这个5次改成100次,就会看到灯是亮一秒灭秒的运行的,符合100X10ms=1秒的分析。原来程序是这样运行的啊!
  1. if (key_stime_ok)               
  2. {
  3.     key_stime_ok = 0;        // 10ms到
  4.        switch(read_key_n())
  5.       {
  6.           case 1:
  7.                  PORTB ^= 1<<2;
  8.                   ++mima[0];
  9.                   break;
  10.            case 2:
  11.                   PORTB ^= 1<<1;
  12.                  break;
  13.       }
  14.   if (++mima[0] >=5)  //这里的两个++被我写重了,本来这句应该只是判断上面的短按是否达到5次的。
  15.         {
  16.            PORTB ^= 1<<7;   
  17.          }
  18. }
复制代码

出0入0汤圆

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

出0入0汤圆

发表于 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的前后顺序调换一下试试看会不会有不同的结果。

出0入0汤圆

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

  1. if (key_stime_ok)               
  2.                    {
  3.                       key_stime_ok = 0;   // 10ms到
  4.                           if (read_key_n()==2)   //若长按2秒
  5.                           {
  6.                               PORTB ^= 1<<2;
  7.                           }
  8.                           if (read_key_n()==1)   // 若短按
  9.                           {
  10.                               PORTB ^= 1<<1;
  11.                           }                  
  12.                    }
复制代码

谢谢!按你的分析,我又对状态机有了新的认识。为了更深刻的了解状态机,我正在对照汇编码,了解此程序中单片机每个机器时钟对代码的运行方式。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电平时的那个值。如果能看懂汇编,也许我就能早明白这个过程了。

出0入0汤圆

发表于 2014-4-10 13:25:30 | 显示全部楼层
不用吧,AVR我也没有学过汇编的。关键是要细心,不能浮躁,更不能想当然,凡事都有个理字。
51的汇编,现在都看不懂了。

出0入0汤圆

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

谢谢!第二版书上将汇编的也不适合我这种初学的,太难看懂了,结合JTAG,我也才看懂
  1. @0000006B: read_key_n
  2. ---- demo_9_2.c -----------------------------------------------------------------------------------
  3. 53:               key_press = key_input;                                // 读按键I/O电平
  4. +0000006B:   931A        ST        -Y,R17         Store indirect and predecrement
  5. +0000006C:   930A        ST        -Y,R16         Store indirect and predecrement
  6. +0000006D:   E000        LDI       R16,0x00       Load immediate
  7. +0000006E:   E0E0        LDI       R30,0x00       Load immediate
  8. +0000006F:   9987        SBIC      0x10,7         Skip if bit in I/O register cleared
  9. +00000070:   E0E1        LDI       R30,0x01       Load immediate
  10. +00000071:   2F1E        MOV       R17,R30        Copy register
复制代码

等 SBIC那行过了后,再放开按键,后面的if,就会把key_press的值认做0了。像我这种初学单片机,不懂汇编,还真得用JTAG配合原汇编代码来一条条代码的看,再用心细细想,才可能明白

出0入0汤圆

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

 楼主| 发表于 2014-4-11 10:36:24 | 显示全部楼层
这个问题我转到另一个帖子了
初学单片机-单键密码锁制作日志
http://www.amobbs.com/thread-5576508-1-1.html
(出处: amoBBS 阿莫电子论坛)
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-18 20:01

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表