Как установить частоту дискретизации для цифрового входа?

adc digital-in

У меня есть АЦП с частотой дискретизации до 40 MSPS, но я использую тактовую частоту 8 МГц, поэтому она будет 8 MSPS. Используемая плата — arduino nano. Проблема в том, что мне нужно делать выборку всего на 2 МГц (из-за емкости переменной массива в arduino Nano, которая составляет около 10 000 элементов в переменной массива). Я знаю, что использование прямого чтения порта увеличит скорость цифрового чтения, но как мне настроить прямое чтение порта на чтение каждые 0,5 мкс? Функция Micros, конечно, не может выполнить эту работу

Спасибо

, 👍1


2 ответа


Лучший ответ:

5

Вам нужно:

  1. Таймер
  2. Доска, способная работать достаточно быстро, чтобы делать то, что вам нужно.

Nano соответствует первому критерию, но не второму.

При 16 МГц вы получаете тактовый период 63 нс. Это означает, что вы получаете всего 8 тактов (фактически дает вам 504 нс) на прерывание от таймера. Это максимум 8 ассемблерных инструкций, которые могут быть выполнены за это время.

Поскольку преамбула процедуры обслуживания прерывания длиннее 7 инструкций, вы можете видеть, что здесь будет небольшая проблема. Нет времени на фактическое чтение цифрового порта ввода-вывода — и если вы используете последовательный протокол для связи с вашим АЦП, вы можете умножить время чтения на удвоенное количество битов, которые вам нужно прочитать. Это долгое время по сравнению с 500 нс.

Так что нет. Подумайте еще раз. Используйте более подходящую плату. Возможно, плата на базе ARM (вроде Teensy 3.x) подойдет лучше.

,

3

Примечание: это попытка ответить на вопрос в том виде, в котором он был задан. Это ответ вряд ли будет полезен первоначальному автору, который вероятно, задал неправильный вопрос. Я пишу это только как способ исследовать пределы того, как быстро скромный AVR может опробовать порт. Для ответ, который действительно пытается решить проблему ОП, см. Ответ Маженко.

Я читаю вопрос следующим образом: можем ли мы попробовать цифровой порт на 2 МГц на Arduino Nano с тактовой частотой 8 МГц? Можем ли мы сделать это, пока сохранение значений в буфере на основе ОЗУ?

Ответ — да, но это нетривиально и требует некоторой сборки. Чтобы увидеть проблему, давайте начнем с попытки сделать это на C++:

uint8_t buffer[1024];

void fill_buffer()
{
    cli();
    for (size_t i = 0; i < sizeof buffer; i++)
        buffer[i] = PINB;
    sei();
}

Обратите внимание, что цикл выполняется с отключенными прерываниями, в противном случае таймер прерывание нарушит синхронизацию цикла. Это переводится как gcc в сборку, эквивалентную этой:

    cli
    ldi  r30, lo8(buffer)      ; load the buffer address into pointer Z
    ldi  r31, hi8(buffer)      ; ditto
0:  in   r24, 0x03             ; read the port
    st   Z+,  r24              ; store into buffer, increment the pointer
    ldi  r24, hi8(buffer+1024) ; save (buffer+1024)>>8 in r24
    cpi  r30, lo8(buffer+1024) ; compare the pointer with buffer+1024
    cpc  r31, r24              ; ditto
    brne 0b                    ; loop back
    sei
    ret

Цикл занимает 8 циклов на итерацию. С тактовой частотой 8 МГц это будет одно считывание в микросекунду. Слишком медленно в два раза.

Можно сэкономить один цикл, используя другой регистр для данных порта. и для условия конца цикла, и путем перемещения третьего ldi из цикл. Еще один цикл можно было бы сэкономить, проверив только старший байт указатель Z, но это потребует выравнивания буфера 256 байтовых границ. С этими двумя оптимизациями нам все еще нужно 6 циклов ЦП на итерацию, т.е. 0,75 мкс при 8 МГц.

Чтобы сделать это быстрее, единственное решение — развернуть цикл. Это можно сделать в ассемблере, используя .rept (что означает «повторить»). директива:

void fill_buffer()
{
    cli();
    asm volatile(
        ".rept %[count]\n"  // повторить (count) раз:
        "in r0, %[pin]\n"   // прочитать входной регистр порта
        "st Z+, r0\n"       // сохранить в ОЗУ
        "nop\n"             // задержка в 1 цикл
        ".endr"
        ::      "z" (buffer),
        [count] "i" (sizeof buffer),
        [pin]   "I" (_SFR_IO_ADDR(PINB))
        : "r0"
    );
    sei();
}

Это занимает 4 цикла или 0,5 мкс на итерацию. Обратите внимание, что пришлось ввести цикл задержки, иначе выборка была бы слишком быстро: 3 цикла или 0,375 мкс на итерацию.

Это не самый быстрый способ. Можно взять один образец за цикл ЦП примерно так:

    in r0, 0x03
    in r1, 0x03
    in r2, 0x03
    ...

Однако этот метод ограничен пакетными показаниями максимум 32 образца.

,