无法对AVR编程以解释Arduino旋转编码器模块的输入

时间:2019-06-27 04:53:25

标签: c avr atmel atmelstudio attiny

我一直在尝试对我的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会闪烁一次500ms,然后程序将再等待500ms,然后再次收听旋转编码器的输出。但是,当我运行此代码时,LED会持续红色持续闪烁一段时间或绿色持续闪烁一段时间,最终从一种颜色切换为另一种颜色,并偶尔出现(单个)蓝色闪烁。每次似乎都是完全随机的,而且似乎完全忽略了我应用于旋转编码器的任何旋转。

我为排除故障所做的事情

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

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

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

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

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

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

谢谢!

3 个答案:

答案 0 :(得分:3)

编码器可以以各种奇怪的方式运行。像任何开关一样,您都会有信号反弹。在某些情况下,转弯期间可能同时激活多个输入。等等,因此您需要定期对其进行采样。

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

答案 1 :(得分:1)

输入短路会无法显示编码器的旋转方向。但是做空的顺序是正确的。

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

enter image description here

因此,通常来说,每个“ CLICK!”运动可以分为四个阶段:

  • 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 
    }
}

答案 2 :(得分:1)

正如其他人指出的那样,机械编码器会弹跳。您需要解决这个问题。

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

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

在那之后,包括一些“停滞时间”,在此期间,您将忽略由于触点弹跳而导致的“时钟”信号的任何其他沿。

算法:

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

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

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

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

4)现在您知道发生了“滴答声”(时钟信号改变),并且指示了方向,请处理

5)禁用读取“时钟”信号(A)一段时间,例如10ms,去抖动;停滞时间过后,转到1)

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

“时钟”信号的边沿检测也可以通过引脚更改中断来执行,您只需在中断发生后的死时间段内将其禁用即可。但是,通常不建议通过引脚更改中断来处理跳动触点,因为跳动(即(非常)快速切换引脚,可能是持续时间为 ns 的脉冲)可能会使硬件混乱。