Comportamento aleatório e imprevisível do comparador analógico

10

Estou trabalhando em um projeto relativamente "simples" em que preciso medir a frequência de uma onda senoidal que varia em amplitude e frequência. Para simplificar, por enquanto, só tenho uma entrada de onda senoidal de frequência fixa (27Hz) (entrada negativa do comparador) que só pode variar em amplitude (usando um potenciômetro). A entrada positiva do comparador é definida como Vcc / 2. A saída do comparador é então alimentada no registro de captura de entrada do microcontrolador atmega2560 para medir a frequência.

O problema é que, em certas amplitudes do sinal de entrada, eu alterno bastante intensamente (ou às vezes, faixas mortas) na saída, que se parece com isso:

insira a descrição da imagem aqui

Onde, como a saída esperada, deve ser algo como isto:

insira a descrição da imagem aqui

Coisas que tentei até agora:

Usando o comparador interno do atmega2560. Usando um comparador externo. Introdução à histerese usando software e circuito de disparo de Schmitt. Tentei várias configurações de entrada, incluindo configuração de referência fixa e configuração do slicer de dados. Tentando diferentes atmega2560's. Tentando diferentes velocidades de relógio.

Algumas soluções eram mais estáveis ​​que outras, mas nenhuma delas era nem de longe aceitável. Eu resolvi com a configuração mais estável até agora:

insira a descrição da imagem aqui

Com essa configuração, certas coisas melhoram / alteram a estabilidade, mas ainda nem de longe são perfeitas:

Alterando o valor de R5 para aumentar a histerese. Removendo completamente o C2 (não faço ideia do porquê). Tocando fios na tábua de pão (alguns deles próximos um do outro). Alternando as fontes de alimentação de externas para USB e vice-versa.

Nesse ponto, é o ruído, meu DAC com o qual estou gerando a onda senoidal ou estou fazendo algo muito fundamental incorretamente. Este circuito funcionou para outras pessoas sem problemas, portanto, algo deve estar errado com minha configuração ou ambiente.

Se alguém tiver alguma sugestão, eu apreciaria muito o seu tempo.

Aqui está minha fonte mínima:

#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);
    }
}

Além disso, aqui está o link para o diagrama de circuitos e a própria biblioteca:

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

ATUALIZAR:

Eu tentei todas as suas sugestões, nenhuma delas funcionou, exceto uma. Limpar os sinalizadores de interrupção ou desativar as interrupções dentro ou fora do ISR realmente não teve nenhum efeito. Pareço entender mal como o registro comparador do chip realmente funciona.

Como mencionei inicialmente, eu usaria a captura de entrada para medir a frequência de uma onda quadrada derivada de uma onda senoidal. A saída do comparador é alimentada no pino de captura de entrada e, em seguida, use temporizadores para medir o período, simples.

Aqui está o diagrama comparador analógico do atmega2560 http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-2549-8-bit-AVR-Microcontroller-ATmega640-1280-1281-2560-2561_datasheet.pdf , página 265:

insira a descrição da imagem aqui

Como você pode ver, o comparador possui duas saídas, ACO e ACIS0 + ACIS1. ACO é definido quando + entrada> - entrada, limpo quando + entrada <- entrada. ACIS0 + ACIS1 são bits de seleção de borda.

O que eu estava fazendo inicialmente estava verificando o tipo de borda no meu ISR. Mudei o ISR para isso:

    ISR(ANALOG_COMP_vect) {

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

E a saída se comportou perfeitamente (exatamente como na segunda foto). Depois, medi a largura dos pulsos, mas os resultados não foram excelentes. Alternância intensa no visor LCD, números saltando para valores aleatórios ou permanecendo em 0, apesar de ter um sinal limpo. Eu reescrevi meu código várias vezes usando condições diferentes, a única solução semi-estável que eu tenho até agora é a seguinte:

#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);
}

Por semi-estável, quero dizer, obtenho o valor correto 1/3 das vezes. Nas outras vezes, 2/3 das vezes é metade do valor correto ou um valor aleatório. Tentei usar os bits de registro do temporizador para instruções condicionais, bem como os bits de registro do comparador no meu ISR, esta é a única configuração que funciona.

O que fiz no final do dia foi usar um comparador externo, com a mesma configuração e fonte (excluindo todas as linhas relacionadas ao comparador). Sua saída foi alimentada no pino de captura de entrada e funcionou como planejado (nem sequer precisava de histerese).

Neste ponto, posso dizer que resolvi usando um comparador externo, mas não tenho idéia do porquê o interno não se comporta. Eu li muitos posts e guias sobre isso, li diferentes bibliotecas, tentei imitá-las sem nenhum resultado aceitável. A folha de dados possui apenas 5 páginas em toda a unidade comparadora, reli-a várias vezes e não vejo o que estou fazendo de errado.

Gostaria de descobrir como usá-lo corretamente, mas se isso falhar, tenho um backup. Se você tiver mais alguma informação, é muito apreciada.

Shibalicious
fonte
4
para iniciantes ... adicione um resistor de 1M entre a saída e a entrada + ve. ESTE é o que cria a histerese, e não o seu R5 ... que apenas muda a referência
JonRB
11
Como você pode produzir imagens de escopo da saída de um comparador que está dentro do chip e não é acessível?
Andy aka
2
Você está desativando outras interrupções ao inserir um ISR? Talvez você precise - pode ser que a maioria dos ISRs esteja recebendo hits duplos.
Andy aka
11
Como você está alternando o pino de histerese e qualificando-o pelo valor atual. O atraso entre a interrupção e a alternância pode estar atrapalhando você.
21418 Trevor_G
11
não mostrada no seu esquema, é a capacitância interna entre o pino 5 e o pino 6; você pode usar o pull-up interno no pino 7 para fazer sua histerese?
Jasen

Respostas:

13

Eu li que você está usando um DAC para gerar o sinal de onda senoidal. As saídas DAC podem apresentar falhas nas alterações do estado de saída, portanto você deve definitivamente aplicar alguma filtragem analógica à saída DAC antes de inseri-la no seu circuito comparador. Isso pode ajudar a evitar alguns dos gatilhos de interrupção dupla que provavelmente estão ocorrendo.

Eu também gostaria de comentar que você realmente deseja usar um comparador externo para esse tipo de problema, para poder aplicar a histerese com resistores sem o uso de uma interação de software. Isso também permitirá um melhor isolamento do problema, pois você pode monitorar diretamente a saída do comparador.

O último comentário refere-se ao tipo de histerese que você está usando. É um pouco difícil ver exatamente qual esquema você está usando, mas observe que o que você quer é um comportamento que faça isso: Você quer a histerese que puxa a tensão limite na direção OPOSTA do que o sinal está em transição. Portanto, para uma borda ascendente, você deseja que o limite seja um pouco maior que o ponto zero e, quando o estado muda, o limite é puxado para um nível mais baixo.

Michael Karas
fonte
11
+1 para a descrição extra de como a direção da histerese deve funcionar. O parágrafo 2 é um bom conselho, mas fazê-lo internamente também é bom, desde que seja feito corretamente, o que neste exemplo não parece ser o caso.
Trevor_G 19/02/19
@Trevor_G -: ^)
Michael Karas
11
@ Hypomania - Eu sei que você pode ler o temporizador único no ISR. Mas, a menos que o timer seja armazenado em buffer duplo para que um registro de saída retenha a contagem de um gatilho enquanto o próprio timer puder continuar a contar, será necessário parar o timer para que você possa lê-lo e reativá-lo após a leitura. . Muitos timers do MCU não têm buffer duplo assim e, portanto, o tempo de processamento para entrar no ISR quando o timer é reativado perde tempo na medição do tempo do período para o próximo meio ciclo. Depende, em certa medida de quão rápido o temporizador estiver a ser sincronizado (continuação)
Michael Karas
11
(continuação acima), mas você nunca quer estar na situação em que está lendo um valor de contagem quando um relógio pode estar chegando ao mesmo tempo para alterar a contagem. Eu não pesquisei o MCU específico que você está usando para verificar se o seu timer é armazenado em buffer duplo em um evento de captura de gatilho ou não.
Michael Karas
11
@ Hypomania - Por um capricho, olhei para a folha de dados do AVR MCU vinculada e vi que a função de captura de entrada do timer é armazenada em buffer duplo !! De fato, o cronômetro nessas partes parece bastante robusto. Faz quase 15 anos desde que eu usei qualquer peça do AVR.
Michael Karas
6

O problema desse cenário é que existe um atraso de tempo entre a troca do comparador e a interrupção sendo tratada até o ponto em que você alterna o pino de "histerese".

Sua banda de histerese também é bastante pequena para esse nível de sinal, considerando o que você está usando. Especialmente quando vejo quanto barulho há nessa onda quadrada no seu osciloscópio.

Com esses dois fatores em mente, existe uma alta probabilidade de que em certos níveis de entrada você obtenha várias arestas do comparador antes de poder lidar com o primeiro. Verificar para ver qual é o estado do comparador durante esse manipulador de interrupções não ajuda muito, pois pode estar em qualquer um dos estados.

Infelizmente, você não detalhou na pergunta como o manipulador funciona.

Seu manipulador deve, no entanto, funcionar mais ou menos assim.

  1. Quando o valor da histerese no estado de limite alto, você deve aguardar uma interrupção negativa da borda.

  2. Quando a interrupção da borda negativa chegar, alterne a histerese para o valor baixo, aguarde alguns ciclos, limpe qualquer interrupção pendente e comece a esperar por uma interrupção positiva da borda.

  3. Quando a interrupção positiva da borda positiva chegar, alterne o pino de histerese para o valor alto, aguarde alguns ciclos, limpe qualquer interrupção pendente e comece a esperar novamente por uma interrupção negativa da borda.

  4. Repita da etapa 1.

BTW, não estou muito interessado no modo como você está usando a referência do comparador como viés para o sinal. Isso resulta em um pouco de conversa cruzada, tanto do sinal para a referência quanto da histerese para o sinal, especialmente com sinais de baixa frequência. Concedido a esses valores que o efeito deve ser pequeno, mas, para pureza, um viés separado no sinal seria melhor.

EDIT: Re seu código.

Na declaração else, você altera a margem de interrupção antes de definir a histerese.

Em nenhum dos casos, você pausa e limpa as interrupções pendentes antes de retornar. (Observe que alterar o registro de controle de interrupção pode criar interrupções por si próprio.)

Não sei se o Atmega interrompe o reentrante, ou seja, se uma borda subsequente interrompe o manipulador ainda em execução da borda anterior. Nesse caso, você precisa lidar com a concorrência adequadamente.

Não sei para que serve a parte PORTC, mas provavelmente precisará mudar para a parte qualificada.

Trevor_G
fonte
Muito obrigado, tentarei suas sugestões amanhã e atualizarei. Quanto ao meu ISR, tenho uma declaração if-else if para o cenário exato que você descreveu, excluindo a espera.
Shibalicious
11
@ Hypomania, você deve editar sua pergunta e publicar seu código de interrupção para que as pessoas possam ver se você está bagunçando algum lugar. Porém, pode ser apenas ruído, os rastreamentos do seu escopo parecem ter mais de 50mV de ruído por lá. Ainda com barulho, eu esperaria que ele se corrigisse, tudo com pulsos extras ocasionais nas transições.
21418 Trevor_G
Era o que eu esperava também. Fará isso o mais rápido possível.
Shibalicious
11
@Hypomania see edit
Trevor_G
11
@ Hipomania Porque você pode ter outra interrupção entre esses dois comandos. Eu também editou a edição ...
Trevor_G
3

Esse efeito é semelhante ao retorno por contato e pode ser mitigado pelas mesmas técnicas de retorno que você usaria para botões de pressão.

  • Decida o tempo de devolução Td
  • mantenha o registro de data e hora da última borda interrompida em uma variável
  • se o tempo entre a interrupção atual e a última for menor que Td, ignore a interrupção atual
Dmitry Grigoryev
fonte