2

我一直在尝试对我的 ATtiny817-XPRO 进行编程以解释来自旋转编码器(Arduino 模块)的输入数据,但是我遇到了一些麻烦,似乎无法弄清楚问题所在。我要做的基本上是对数字组合锁进行编程,每次旋转编码器旋转一个“滴答”(在任一方向)时,红色 LED 都会闪烁,一旦检测到正确的“组合”,就会闪烁绿色 LED . 它比这更复杂一些,所以当我在测试我的代码时遇到麻烦时,我决定编写一个简单的方法来帮助我解决/调试问题。我已将其包括在下面:

void testRotaryInput(){
    if(!(PORTC.IN & 0b00000001)){   // if rotary encoder is turned clockwise
        PORTB.OUT = 0b00000010;     // turn on green LED
    }
    else if(!(PORTC.IN & 0b00000010)){  // if rotary encoder is turned CCW
        PORTB.OUT = 0b00000001;         // turn on blue LED
    }
    else{                           // if rotary encoder remains stationary
        PORTB.OUT = 0b00000100;     // turn on red LED
    }
    RTC.CNT = 0;
    while(RTC.CNT<16384){}          // wait 500ms
    PORTB.OUT = 0x00;               // turn LED off
    while(RTC.CNT<32768){}          // wait another 500ms
}

int main(void)
{
    PORTB.DIR = 0xFF;               // PORT B = output
    PORTC.DIR = 0x00;               // PORT C = input
    RTC.CTRLA = RTC_RTCEN_bm;       // Enable RTC
    PORTB.OUT = 0x00;               // Ensure all LEDs start turned off
                                    // ^(not necessary but I do it just in case)^

    //testLED(); <-- previous test I used to make sure each LED works upon start-up

    while(1)
    {
        testRotaryInput();
    }
}

这里的想法是,无论哪条输出线首先到达 AVR,都应该指示旋转编码器的旋转方向,因为这决定了两个信号之间的相移。根据旋转方向(或没有旋转方向),红色/绿色/蓝色 LED 将闪烁一次 500 毫秒,然后程序将再等待 500 毫秒,然后再次收听旋转编码器输出。但是,当我运行此代码时,LED 将持续闪烁一段时间的红色或绿色,最终从一种颜色切换到另一种颜色,偶尔(单次)蓝色闪烁。这似乎每次都是完全随机的,并且似乎完全忽略了我应用于旋转编码器的任何旋转。

我为解决问题所做的事情:

  • 将旋转编码器的两个输出连接到示波器以查看是否有任何输出(一切看起来都应该)

  • 使用外部电源为旋转编码器供电,因为当我连接到 ATtiny817-XPRO 时,我只能从 5.0V VCC 引脚读取 1.6V(我怀疑这是因为 LED 和旋转编码器可能消耗太多当前的)

  • 测量来自所述电源的电压以确保旋转编码器接收到 5.0V(我测量到大约 4.97V)

  • 检查以确保电路正确并正常工作

不幸的是,这些事情都没有解决手头的问题。因此,我怀疑我的代码可能是罪魁祸首,因为这是我第一次尝试使用旋转编码器,更不用说解释由旋转编码器生成的数据了。但是,如果我的代码看起来应该可以正常工作,我将不胜感激,这样我就可以将精力集中在其他地方。

任何人都可以阐明可能导致此问题的原因吗?我不认为这是一块有问题的电路板,因为我在两天前将这些引脚用于不同的应用程序而没有任何问题。此外,在 AVR 方面,我还是个新手,所以我确信我的代码远未达到应有的健壮程度。

谢谢!

4

3 回答 3

3

编码器可以以各种奇怪的方式表现。你会像任何开关一样有信号反弹。您会遇到几个输入可能在一个回合中同时处于活动状态的情况。等等。因此您需要定期对它们进行采样。

创建一个定时器,每 5ms 左右轮询一次编码器。始终存储上一次读取。在连续两次读取稳定之前,不要接受对输入的任何更改为有效。这是最简单的数字滤波器形式。

于 2019-06-27T07:00:37.577 回答
1

正如其他人所指出的,机械编码器会出现弹跳。你需要处理它。

读取这种编码器的最简单方法是将其中一个输出(例如 A)解释为“时钟”信号,将另一个输出(例如 B)解释为方向指示器。

您等待“时钟”输出的下降(或上升)沿,当检测到一个时,立即读取另一个输出的状态以确定方向。

之后,包括一些“死区时间”,在此期间您忽略由于触点弹跳而出现的“时钟”信号的任何其他边沿。

算法:

0)读取“时钟”信号(A)的状态并存储(“先前的时钟状态”)

1) 读取“时钟”信号 (A) 的状态并与“之前的时钟状态”进行比较

2)如果时钟信号没有改变,例如从高到低(如果你想使用下降沿),转到1)。

3)读取“方向”信号(B)的状态,将时钟的当前状态存储到“先前的时钟状态”

4)现在您知道发生了“滴答声”(时钟信号变化)和方向,处理它

5) 禁止读取“时钟”信号 (A) 一段时间,例如 10 毫秒,以防抖动;死区时间过去后,转到 1)

这种方法不是时间关键的。只要您确保轮询“时钟”的速度至少是信号 A 的变化和信号 B 的相应变化之间的最短时间的两倍(减去 A 的弹跳时间),您希望看到(取决于最大预期转速)它将绝对可靠地工作。

“时钟”信号的边沿检测也可以通过引脚更改中断执行,您只需在中断发生后的死区时间段内禁用该中断即可。然而,通常不建议通过引脚更改中断处理弹跳接触,因为弹跳(即(非常)快速切换引脚,可以是纳秒持续时间的脉冲)可能会混淆硬件。

于 2019-06-28T11:10:56.557 回答
1

哪个输入短路不会显示编码器转动的方向。但是它们被做空的顺序确实如此。

通常,旋转编码器有两个与接地引脚短路的输出:第一次短路,然后第二次短路,然后第一次释放,然后第二次释放 - 这个完整的序列发生在每次点击之间。(当然,有些编码器在序列中间有额外的“点击”,或者根本没有点击,但它们中的大多数都像上面描述的那样)。

在此处输入图像描述

所以,一般来说,每次“点击!” 您可以将运动视为 4 个阶段:

  • 0.两个输入都被释放(高) - 默认位置
  • 1.输入 A 对地短路(低),输入 B 释放(高)
  • 2.两个输入都短路(低)
  • 3.输入 A 释放(高),B 短路(低)。

向一个方向旋转是通过阶段 0-1-2-3-0 的通道。另一个方向是0-3-2-1-0。因此,无论编码器旋转的方向是什么,两个输入都将在某个特定时刻短接到地。

从上图中可以看出,通常弹跳仅发生在其中一个输入处。因此,您可以将反弹视为两个相邻相位之间的跳跃,这使得去抖动变得更加简单。

由于这些阶段的变化非常快,因此您必须非常快地汇集输入引脚,可能是每秒 1000 次,以处理快速旋转。

处理旋转的代码可能如下:

signed int encoder_phase = 0;

void pull_encoder() {
    int phase = ((PORTC.IN & (1 << INPUT_A_PINNO)) ? 0 : 1)
                ^ ((PORTC.IN & (1 << INPUT_B_PINNO)) ? 0 : 0b11);
    // the value of phase variable is 0 1 2 or 3, as described above
    int phase_shifted = (phase - encoder_phase) & 3;
    if (phase_shifted == 2) { // jumped instantly over two phases - error
        encoder_phase = 0;
    } else if (phase_shifted == 1) { // rotating forward
        encoder_phase++;
        if (encoder_phase >= 4) { // Full cycle
            encoder_phase = 0;
            handle_clockwise_rotation();
        }
    } else if (phase_shifted == 3) { // rotating backward; 
        encoder_phase--;
        if (encoder_phase <= -4) { // Full cycle backward
            encoder_phase = 0;
            handle_counterclockwise_rotation();
        }
    }
    if (phase == 0) {
        encoder_phase = 0; // reset 
    }
}
于 2019-06-27T11:14:09.957 回答