随机和不可预测的模拟比较器行为

电器工程 微控制器 运算放大器 avr 噪音 成分
2022-01-09 23:04:47

我正在做一个相对“简单”的项目,我需要测量幅度和频率变化的正弦波的频率。为简化起见,目前,我只有一个固定频率 (27Hz) 正弦波输入(比较器的负输入),其幅度只能改变(使用电位计)。比较器的正输入设置为 Vcc/2。然后将比较器的输出馈入 atmega2560 微控制器的输入捕捉寄存器以测量频率。

问题是,在输入信号的某些幅度下,我在输出上得到非常强烈的切换(或有时是死区),如下所示:

在此处输入图像描述

预期输出应如下所示:

在此处输入图像描述

到目前为止我尝试过的事情:

使用内部 atmega2560 的内部比较器。使用外部比较器。使用软件和施密特触发器电路引入迟滞。尝试了各种输入设置,包括固定参考设置和数据切片器设置。尝试不同的atmega2560。尝试不同的时钟速度。

有些解决方案比其他解决方案更稳定,但没有一个是可以接受的。到目前为止,我已经解决了最稳定的配置:

在此处输入图像描述

使用此设置,某些事情会改善/改变稳定性,但仍远非完美:

改变 R5 的值以增加滞后。完全移除 C2(不知道为什么)。触摸面包板上的电线(其中相当多的电线彼此相邻)。将电源从外部切换到 USB,反之亦然。

在这一点上,要么是噪音,要么是我用来生成正弦波的 DAC,要么是我在做一些非常基本的错误。该电路对其他人有效,没有任何问题,因此我的配置或环境一定有问题。

如果有人有任何建议,我将不胜感激您的时间。

这是我的最小来源:

#include <avr/io.h>

void init(void);

void init(void) {
    /* Setup comparator */
    ACSR = (1 << ACIE) | (1 << ACIS1);
    /* Initialize PORTD for PIND5 */
    DDRD = 0x00;
    PORTD = 0x00;
    /* Enable global interrupts */
    sei();
}

int main(void) {

    init();

    while (1) {}
}

ISR(ANALOG_COMP_vect) {

     if (!(ACSR &  (1<<ACIS0))) { //comparator falling edge
         /* Set PIND5 to 0V */
         PORTD &= ~(1 << PIND5);

         ACSR |= (1<<ACIS0); //set next comparator detection on rising edge
    }
    else  {
       ACSR &= ~(1<<ACIS0); //set next comparator detection on falling edge
       /* Set PIND5 to 5V */
       PORTD |= (1 << PIND5);
    }
}

此外,这是电路图和库本身的链接:

http://interface.khm.de/index.php/lab/interfaces-advanced/frequency-measurement-library/

更新:

我已经尝试了您的所有建议,但没有一个有效,只有一个。清除中断标志或禁用 ISR 内部或外部的中断实际上并没有任何效果。我似乎误解了芯片的比较器寄存器实际上是如何工作的。

正如我最初提到的,我打算使用输入捕获来测量从正弦波派生的方波的频率。比较器的输出馈入输入捕捉引脚,然后使用定时器测量周期,简单。

这是 atmega2560 http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2549-8-bit-AVR-Microcontroller-ATmega640-1280-1281-2560-2561_datasheet.pdf的模拟比较器图,第 265 页:

在此处输入图像描述

如您所见,比较器有两个输出,ACO 和 ACIS0+ACIS1。ACO 在 + 输入 > - 输入时置位,在 + 输入 < - 输入时清零。ACIS0+ACIS1 是边沿选择位。

我最初做的是检查 ISR 中的边缘类型。我改为将 ISR 更改为:

    ISR(ANALOG_COMP_vect) {

     if (!(ACSR &  (1<<ACO))) { // + < -
         /* Set PIND5 to 0V */
         PORTD &= ~(1 << PIND5);
    }
    else  {
       /* Set PIND5 to 5V */
       PORTD |= (1 << PIND5);
    }
}

并且输出表现得完美无缺(就像第二张图片一样)。然后我开始测量脉冲的宽度,但结果不是很好。尽管有清晰的信号,我的 LCD 显示屏上的强烈切换,数字跳到随机值或保持在 0。我使用不同的条件多次重写了我的代码,到目前为止我得到的唯一半稳定的解决方案是:

#include <avr/io.h>
#include <util/delay.h>
#include "UART.h"

void init(void);

volatile uint16_t y = 0;
volatile uint16_t x = 0;
volatile uint16_t current_value = 0;
volatile uint16_t previous_value = 0;
volatile uint16_t total = 0;

void init(void) {
    /* Normal mode, 64 prescaler, Rising Edge trigger, Input Capture */
    TCCR1A = 0;
    TCCR1B = (1 << CS10) | (1 << CS11) | (1 << ICES1);
    TIMSK1 = (1 << ICIE1);

    ACSR = (1 << ACIC);
    ADCSRB = 0x00;

    /* This port is used for simulating comparator's output */
    DDRC = 0xFF;
    PORTC = 0xFF;

    DDRD = 0x00;
    PORTD = 0x00;

    USART_Init(UBRR_VALUE);

    sei();
}

int main(void) {

init();

    while (1) {
        if (TCNT1 == 60000) {
            /* Display the values on the LCD */
            USART_Transmit(0xFE);
            USART_Transmit(0x01);

            USART_Transmit_Double(x+y);
        }
    }
}

ISR(TIMER1_CAPT_vect) {

    //ACSR &= ~(1<<ACIC);

    if (!(ACSR & (1 << ACO))) {
        if (!(TCCR1B & (1 << ICES1))) { // check for falling edge
            PORTD |= (1 << PIND5);

            PORTC &= ~(1 << PINC1);

            TCCR1B |= (1 << ICES1);

            current_value = ICR1;
            x = current_value - previous_value;
            previous_value = current_value;
        }
    }        
    else {
        if (TCCR1B & (1 << ICES1)) { // check for rising edge
            PORTD &= ~(1 << PIND5);

            PORTC |= (1 << PINC1);

            TCCR1B &= ~(1 << ICES1);

            current_value = ICR1;
            y = current_value - previous_value;
            previous_value = current_value;
        }
    }

    //ACSR |= (1<<ACIC);
}

我的意思是半稳定,我得到正确值的 1/3 次。其他时间的 2/3 是正确值的一半或随机值。我尝试在 ISR 中使用定时器的寄存器位作为条件语句以及比较器的寄存器位,这是唯一可行的配置。

我当天晚些时候做的是使用外部比较器,而不是使用相同的设置和源(不包括与比较器相关的所有行)。它的输出被送入输入捕捉引脚,它按预期工作(甚至不需要任何滞后)。

在这一点上,我可以说我通过使用外部比较器解决了这个问题,但是我不知道为什么内部比较器不表现自己。我已经阅读了很多关于这方面的帖子和指南,阅读了不同的库,试图模仿它们而没有任何可接受的结果。整个比较器单元的数据表只有 5 页,我重读了很多次,我看不出我做错了什么。

我想知道如何正确使用它,但如果失败,我有一个备份。如果您有任何进一步的意见,我们将不胜感激。

4个回答

我读到您正在使用 DAC 来生成正弦波信号。DAC 输出可能会在输出状态变化时出现毛刺,因此在将其馈入比较器电路之前,您绝对应该对 DAC 输出应用一些模拟滤波。这有助于防止某些可能发生的双中断触发。

我还要评论说,您确实希望对此类问题使用外部比较器,这样您就可以在不使用软件交互的情况下使用电阻器应用滞后。这也将允许更好的问题隔离,因为您可以直接监控比较器的输出。

最后一条评论与您使用的滞后类型有关。很难确切地看到您正在使用什么方案,但请注意,您想要的是执行此操作的行为:您想要将阈值电压拉向相反方向的滞后,而不是信号正在转换。因此,对于上升沿,您希望阈值比零点高一点,然后当状态改变时,阈值被拉到较低的水平。

这种情况的问题是比较器切换和处理中断到切换“迟滞”引脚之间存在时间延迟。

考虑到您使用它的目的,您的滞后带对于该信号电平也相当小。尤其是当我看到示波器上的方波上有多少噪音时。

考虑到这两个因素,在某些输入电平下,您很可能会在处理第一个边缘之前从比较器获得多个边缘。在中断处理程序期间检查比较器的状态不会有太大帮助,因为它可以处于任何一种状态。

不幸的是,您没有详细说明处理程序是如何工作的。

但是,您的处理程序应该像这样工作。

  1. 当迟滞值处于高阈值状态时,您应该等待下降沿中断。

  2. 当所述下降沿中断到达时,将滞后切换到低值,等待几个周期,然后清除任何未决中断并开始等待上升沿中断。

  3. 当所述上升沿中断到达时,将迟滞引脚切换回高值,等待几个周期,清除任何未决中断并再次开始等待下降沿中断。

  4. 从步骤 1 开始重复。

顺便说一句,我不太喜欢您使用比较器参考作为信号偏差的方式。这会导致从信号到参考以及从滞后到信号的少量串扰,尤其是低频信号。考虑到那些影响应该很小的值,但为了纯度,信号上的单独偏差会更好。

编辑:重新你的代码。

在 else 语句中,您在设置滞后之前更改中断沿。

在这两种情况下,您都不会在返回之前暂停并清除任何挂起的中断。(注意,更改中断控制寄存器可以自行创建中断。)

我不知道 Atmega 是否会进行重入中断,即后续边缘是否会从前一个边缘中断仍在运行的处理程序。如果是这样,您需要适当地处理并发。

不确定 PORTC 部分的用途,但它可能需要移入合格的部分。

这种效果类似于触点弹跳,并且可以通过您用于按钮的相同去抖技术来减轻。

  • 决定去抖时间Td
  • 将最后一个边缘中断的时间戳保存在变量中
  • 如果当前中断与上一个中断之间的时间小于Td,则忽略当前中断

在此处输入图像描述TI 的此设计说明准确解释了噪声信号的问题以及添加适当迟滞的必要性。正如@Michael Karas 建议的那样,它当然是使用外部比较器完成的,并且还解决了负输入信号转换的多次触发,即@Trevor_G 和@Dmitry Grigoryev 在他们的答案中概述的“接触反弹”问题https:// www.ti.com/lit/ug/tidu020a/tidu020a.pdf?ts=1621988309888&ref_url=https%253A%252F%252Fwww.google.com%252F