Как установить частоту дискретизации для цифрового входа?
У меня есть АЦП с частотой дискретизации до 40 MSPS, но я использую тактовую частоту 8 МГц, поэтому она будет 8 MSPS. Используемая плата — arduino nano. Проблема в том, что мне нужно делать выборку всего на 2 МГц (из-за емкости переменной массива в arduino Nano, которая составляет около 10 000 элементов в переменной массива). Я знаю, что использование прямого чтения порта увеличит скорость цифрового чтения, но как мне настроить прямое чтение порта на чтение каждые 0,5 мкс? Функция Micros, конечно, не может выполнить эту работу
Спасибо
@Zahi Azmi, 👍1
2 ответа
Лучший ответ:
Вам нужно:
- Таймер
- Доска, способная работать достаточно быстро, чтобы делать то, что вам нужно.
Nano соответствует первому критерию, но не второму.
При 16 МГц вы получаете тактовый период 63 нс. Это означает, что вы получаете всего 8 тактов (фактически дает вам 504 нс) на прерывание от таймера. Это максимум 8 ассемблерных инструкций, которые могут быть выполнены за это время.
Поскольку преамбула процедуры обслуживания прерывания длиннее 7 инструкций, вы можете видеть, что здесь будет небольшая проблема. Нет времени на фактическое чтение цифрового порта ввода-вывода — и если вы используете последовательный протокол для связи с вашим АЦП, вы можете умножить время чтения на удвоенное количество битов, которые вам нужно прочитать. Это долгое время по сравнению с 500 нс.
Так что нет. Подумайте еще раз. Используйте более подходящую плату. Возможно, плата на базе ARM (вроде Teensy 3.x) подойдет лучше.
Примечание: это попытка ответить на вопрос в том виде, в котором он был задан. Это ответ вряд ли будет полезен первоначальному автору, который вероятно, задал неправильный вопрос. Я пишу это только как способ исследовать пределы того, как быстро скромный 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 образца.
- Постоянный выход тактовой частоты Arduino
- Расширение аналоговых входов для Arduino
- ESP8266: system_adc_read_fast() всегда возвращает 1024
- Параллельное чтение нескольких выводов DI как одного байта/слова
- Какое напряжение и ток может потреблять цифровой вход Arduino Uno?
- Использование контактов NodeMCU D8 (GPIO15), D4 (GPIO2) и D3 (GPIO0).
- Высокоскоростной внешний АЦП
- Измерение напряжения литий-ионного элемента, используемого для питания Arduino через повышающий модуль