【问题标题】:Why does this state machine not maintain its state?为什么这个状态机不保持它的状态?
【发布时间】:2014-03-05 03:56:20
【问题描述】:

嘿,StackOverflow!

在下面的代码中,我有一个简单的状态机,它改变了一些外部照明设备的操作(正如 cmets 所暗示的那样)。通过按下连接到 GP1 的按钮改变状态。连接到 GP1 的电路是一个比较器去抖动电路,它将 VDD 与 0.6VDD 进行比较(我也尝试过 RC/二极管/施密特触发电路),然后强制信号 LO。在示波器上,当按钮被快速按下时,我们会看到一个干净的方波。

PIC10F200的当前(和不希望)行为如下:

  1. 开关被按下(状态 = 0)
  2. 状态机变量增量(状态 = 1)
  3. 照明转到案例 1,然后打开
  4. 照明至少持续一秒钟
  5. 灯光关闭
  6. 系统保持在此状态,直到再次按下按钮或 关机

问题是:为什么会这样?如果可能的话,我如何修复它,使单次按下按钮等同于单次状态增量,然后 PIC 会保持这种增量只要系统通电且按钮未再次启动?

#define SYS_FREQ        8000000L
#define FCY             SYS_FREQ/4
#define _XTAL_FREQ      4000000

/******************************************************************************/
/* User Global Variable Declaration                                           */
/******************************************************************************/


/******************************************************************************/
/* Main Program                                                               */
/******************************************************************************/

__CONFIG(MCLRE_ON & CP_OFF & OSC_IntRC);

void main(void)
{
    TRIS = 0b111110;

    unsigned char state = 0;

    while(1)
    {
        switch (state)
        {
            case 0: // IDLE/OFF
                if (GPIObits.GP0) GPIObits.GP0 = 0;
                break;
            case 1: // ON
                if (!GPIObits.GP0) GPIObits.GP0 = 1;
                break;
            case 2: // BLINK (slow)
                GPIObits.GP0 = !GPIObits.GP0;
                __delay_ms(100);
                break;
            case 3: // BLINK (fast)
                GPIObits.GP0 = !GPIObits.GP0;
                __delay_ms(50);
                break;
            case 4: // BEAT DETECT
                GPIObits.GP0 = GPIObits.GP2;
                break;
            default:
                state = 0;
                break;
        }

        if (!GPIObits.GP1)
        {
            __delay_ms(250);
            state++;
        }
    }
}

更新:由于我试图用这个代码/系统完成什么似乎有点混乱,让我们提供完整的上下文。 PIC10F200 微控制器是电致发光 (EL) 线驱动器整体电路板设计的一部分。微控制器只需将GP0连接到驱动IC的EN端口即可控制驱动电路是否启用。 系统有四种工作模式,线常亮、线闪烁、线闪烁快、检测到低频节拍时线闪烁(系统中的另一个电路)。 这些操作模式的转换由安装在 PCB 上的按钮(瞬间打开)开关控制。 这需要上面代码中的 state 在按钮驱动之间保持稳定。它目前不这样做,其行为如本文原始部分所述。正如问题标题所述,为什么state 目前不稳定,我该如何做到这一点?

更新(2014-03-08):解决方案

需要设置以下设置,假设 GP0 是输出,GP2 是您的 T0CKI,并且您有一个在启动时将信号驱动到 LO 的开关。

TRIS = 0b111110;
OPTION = 0b11101111;

OPTION 的 0-3 位是否真正重要是一个判断调用,以及您是否选择使用 WDT 模块。

此外,按钮释放检测的实现是一种简单的计数器机制,在计数期间的任何时间点 GP2 为 LO 时都会重置。

if (TMR0 > 0)
{
    while (count < 20)
    {
        if (!GPIObits.GP2) count = 0;
        __delay_ms(10);
        count++;
    }
    TMR0 = 0;
    state++;
}

【问题讨论】:

  • 那么gp1是按钮,gp0是led,gp2是什么?另外,按钮是如何工作的?一键(按下并释放)给 1,再点击 0?
  • 是的,GP1 连接到按钮,GP0 连接 LED/EL 线驱动器 EN 端口(最终),GP2 是低频检测电路的输入(低音来自...响亮的音乐) ,尚未连接,因为我什至无法让状态机正常工作。该按钮是一个即时按钮,就像您在大多数廉价电子产品中找到的真正基本的按钮一样。因此,当它被按下时,触点被连接并且 VDD 进入比较器电路(LM324N 运算放大器)。
  • 你为什么要用“状态”来做。你想要达到的目标很容易。您不需要使代码复杂化。
  • 使用state,我试图简单地在代码中标记的设备操作模式之间切换。以我目前对 PIC、XC8 的工作原理和 C 的了解,这是我能想到的完成这项任务的最简单方法。我完全愿意尝试不同的方法,除了汇编中的任何解决方案,因为改变语言不仅不能解决问题,它还引入了不必要的实现和支持复杂性。
  • 你描述的是一个状态。按下按钮时。将来您可能想要添加其他内容,但现在它是一种状态。 if (!GPIObits.GP1){GPIObits.GP0 = 1; __delay_ms(1000); GPIObits.GP0 = 0;}

标签: c microcontroller pic microchip xc8


【解决方案1】:

您有硬件/软件设计问题!

  1. 当您的程序处于延迟循环时,您的按键按钮不在 检查!
  2. 您只检查按键事件,但您还必须检查按键 释放。

我的目的是您可以使用 GP2 (T0CKI) 引脚代替 GP1 作为按键按钮。如果用作计数器 TMR0 输入,则该引脚具有施密特触发器输入。之后将您的 MCPU TMR0 配置为在 GP2 (T0CKI) 引脚上使用外部时钟的计数器。您还必须设置 T0SE 位来配置计数器,该计数器将在 T0CKI 引脚上从高到低转换时递增。 在任何循环后的程序中检查 TMR0 内容是否大于 0 按键被按下。 等待几毫秒,然后检查是否释放了密钥,如果释放则增加state 变量并清除 TMR0 内容。

【讨论】:

  • 您的第一点完全正确(+1)。我的代码只关心第二个。如果 LED 的间隔时间很长,那么你有一个很大的问题。您的解决方案很有趣,但很不正统。处理这类问题的方法是使用计时器。检查计时器(情况 2 和 3 没有延迟),如果时间到期,则 LED(开或关)并重置计时器。
  • 谢谢!实际上我说的是定时器TMR0配置为通过引脚GP2(T0CKI)触发的计数器,所以你必须按下按钮256次才能将计数器变为0。但你的方式也是可能的!
  • @GJ :所以OPTION = 0b11111111; 现在,根据文档,T0CS 可以正确使用GP2/T0CKI,T0SE 与GP2 进行异或运算以产生输入多路复用器。因此,对于 HI -> LO 转换,它适合 T0SE 为 1 和 GP2 达到 HI 以馈送 MUX LO。预分频器设置是否与此处相关?
  • @James Lui:如果您清除option 寄存器中的PSA 位,您也可以包含prascaler 单元,但没有必要!使用分配给WDT 的pracaler 在这种情况下,当您在延迟循环中等待时,您可以将 WDT 用于更长的时间间隔。
  • 哦,我们现在很接近了。我设置了OPTION = 0b1110111;,这似乎会产生稳定的状态变化,但是如果我过多地按下按钮,程序就会挂起。由于它是一个简单的程序,因此有两种可能性。 1) 它挂在我用来检查GP2 是否始终为HI 的代码部分内,然后再重置TMR0 并增加状态。或 2) 我无意中停止了状态转换并且程序卡在IDLE 这不太可能。 @GJ 和 @valter :我们的辩证法和您的两个建议都将我们带到了这里,谢谢!
【解决方案2】:

移动你的

if (!GPIObits.GP1){
    if(isPressed == false){
        //__delay_ms(250); //you dont need the delay
        state++;
        if(state == 5){state = 0;}
        isPressed = true;
    }
}
else{isPressed = false;}

在switch 语句之前。 char isPressed = false; 在 while 循环之前

变态

【讨论】:

  • 没有必要使用额外的变量isPressed我的解决方案很好,如果state大于4默认case语句应该设置为0。
  • @GJ。 应该将其设置为 0,如果按下按钮,它将变为 1。所以你失去了零状态。另一件事是,在微控制器中,您不使用 250 毫秒的延迟来检查按钮是否被按下。你有 schmitt trigger 来处理这个问题。
  • 不,你不会失去 0 状态。当变量state 增加 5 时,默认 case 语句应将其设置为 0,然后下一个循环应将 GP0 设置为 0 并关闭 LED。
  • @GJ。你没明白我的意思。假设按下按钮(GPIObits.GP1 = 0)和state = 5。开关转到默认值并设置state = 0,然后if statement 为 true 并将状态增加到 1。下一次迭代不会转到 0,而是转到 1。它也不是 |,而是||.
  • 无需设置为 0,因为这应该关闭 GP0 和 LED,但下一个循环将再次将其设置为 1。感谢错误或操作员!
【解决方案3】:
__delay_ms(250); <-- too small delay

当您缓慢按下按钮时,循环可能会旋转数次。尝试将其增加到 1000 毫秒。

更新

您应该在禁用 WDT(看门狗定时器)的情况下运行 PIC,否则它将在几秒钟内重置 pic。这与您的观察相似。您可以在 main 函数开始时闪烁 LED 以检查是否发生这种情况,或者您可以初始化 unsigned char state = 1; 然后查看行为。

试试这个__CONFIG(MCLRE_ON &amp; CP_OFF &amp; OSC_IntRC &amp; WDT_OFF);

【讨论】:

  • 我之前试过了,系统还是一样,即使我把它设置成任意大的东西,比如... 10000ms,还是一样,它会亮,持续一秒钟,然后它会关闭并保持这种状态......
  • 我怀疑图片在亮起时是否会重置。你怎么给灯供电。灯是否消耗大量功率或引入任何故障或干扰,或者是否有任何继电器?您是否使用好的 PSU 进行拍照?
  • 为了测试,我正在使用一个 LED……它应该会消耗非常……非常……很小的电流。输出最终将启用的电路将消耗更少。 PSU 基本上是 USB(5V,500mA)。
  • 您是否为 GP1 连接了上拉电阻以阻止其浮动?尝试直接将按钮连接到 GP1 跳过比较器去抖电路。这里不需要去抖动,因为您使用的是 250 毫秒的延迟。
  • 我猜你的比较器输出低电平有效。你检查了吗?
猜你喜欢
  • 2020-12-16
  • 2011-07-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-15
  • 2013-07-02
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多