DWT для измерения времени между нарастающим фронтом двух пульсовых волн

Я подал два прямоугольных сигнала на вход цифровых выводов Arduino Due и пытаюсь найти задержку распространения между ними. Для этого вместо использования micros() мне нужны наносекунды, поэтому используется DWT_CYCCNT. Но если задержка между ними равна нулю (оба сигнала — идеальные прямоугольные), то задержка, полученная с помощью этого кода, составляет 770 нс. Мне просто интересно ваше мнение о том, подходит ли подход с использованием DWT для этой цели и как импровизировать.

#define REG_DWT_CTRL   (*(volatile unsigned int*)0xE0001000U)  // Регистр управления DWT
#define REG_DWT_CYCCNT (*(volatile unsigned int*)0xE0001004U)  // Регистр счетчика циклов DWT
#define REG_DEMCR      (*(volatile unsigned int*)0xE000EDFCU)  // Отладка исключений и регистр управления монитором

#define CYCCNTENA (1 << 0)  // Бит включения счетчика циклов DWT CTRL
#define TRCENA    (1 << 24) // Бит включения трассировки DEMCR

#define INPUT_PIN 4  // Буферный ввод
#define OUTPUT_PIN 5 // Выход буфера

volatile uint32_t start_cycles = 0;
volatile uint32_t end_cycles = 0;
volatile bool measurement_ready = false;

void inputISR() {
  start_cycles = REG_DWT_CYCCNT;  // Захват циклов по переднему фронту входного сигнала
  measurement_ready = false;      // Сброс до срабатывания выхода
}

void outputISR() {
  end_cycles = REG_DWT_CYCCNT;    // Захват циклов по переднему фронту выходного сигнала
  measurement_ready = true;       // Измерение сигнала готово
}

void setup() {
  // Включить функцию трассировки
  REG_DEMCR |= TRCENA;
  
  // Сбросить счетчик циклов на 0 (необязательно)
  REG_DWT_CYCCNT = 0;
  
  // Включить счетчик циклов
  REG_DWT_CTRL |= CYCCNTENA;

  pinMode(INPUT_PIN, INPUT);
  pinMode(OUTPUT_PIN, INPUT);

  // Добавить прерывания для нарастающих фронтов (буфер: подъем входного сигнала -> подъем выходного сигнала)
  attachInterrupt(digitalPinToInterrupt(INPUT_PIN), inputISR, RISING);
  attachInterrupt(digitalPinToInterrupt(OUTPUT_PIN), outputISR, RISING);

  Serial.begin(115200);
}

void loop() {
  if (measurement_ready) {
    uint32_t cycle_diff = end_cycles - start_cycles;
    float delay_ns = cycle_diff * (1000.0 / 84.0); // Преобразовать в нс (84 МГц)
    Serial.print("Propagation delay (ns): ");
    Serial.println(delay_ns);
    measurement_ready = false; // Сброс для следующего измерения
  }
}

, 👍2


2 ответа


0

Поскольку процедуры обработки прерываний (ISR) не могут выполняться одновременно, ваш подход имеет изъян. Он работает, пока интервал между фронтами не меньше времени выполнения первой ISR.

Решение — перехватить счётчик аппаратно. К сожалению, микроконтроллер Due не имеет такой аппаратной поддержки.

Кроме того, согласно этому обзору в Википедии, задержка прерывания составляет 12 тактов. Это приводит к неопределённости (джиттеру) при считывании показаний счётчика в программе. Пожалуйста, учтите это.

Поэтому вам нужно придумать другой алгоритм или использовать другое устройство.

,

1

Стоит помнить, что тактовая частота Due составляет 84 МГц. Что бы вы ни делали с различными счётчиками, это будет происходить с той же скоростью или с какой-то её частью. Поэтому, когда вы говорите:

Мне нужно измерение наносекунд

Ну, отдельный цикл на частоте 84 МГц занимает около 11,9 наносекунд. Без каких-либо трюков с передискретизацией или чего-то подобного любое измерение будет иметь это разрешение в 11,9 наносекунд, которое в несколько раз хуже; в большинстве случаев гораздо хуже.

Мне просто интересно ваше мнение о том, является ли подход с использованием DWT правильным для этой цели

Что сказала эта занятая пчелка.

Всё, что я могу добавить относительно DWT, это то, что он, похоже, действительно предназначен для отладки/тестирования производительности и не предназначен для использования в обычном коде приложений/набросков, как вы пытаетесь его использовать.

и как импровизировать.

Далее следует попытка импровизации. Я не готов к тщательному тестированию. Вам нужно будет оценить это самостоятельно. Я почти уверен, что вы всё равно не сможете нас убедить в этом из-за упомянутого ранее недостатка разрешения.

Периферийные модули таймера/счётчика в микроконтроллере SAM8XE Due можно использовать для измерения сигналов. Насколько мне известно, на Due они работают только на частоте до 42 МГц (половина тактовой частоты). Таким образом, каждый такт таймера-счётчика длится примерно 23,8 наносекунды.

Это объясняется комментариями и явными названиями в коде ниже, но основная идея заключается в настройке таймера-счётчика таким образом, чтобы он более или менее напрямую реагировал на сигналы, поступающие на два контакта. То есть, время выполнения кода не учитывается при измерении. Это часто называют «захватом входного сигнала». В техническом описании микроконтроллера Due это называется просто «режимом захвата».

Используемые вами выводы — один из немногих наборов выводов, с помощью которых можно реализовать подобный захват. А именно, это выводы TIOA и TIOB, подключенные к одному из счётчиков таймера микросхемы. В вашем случае TIOA6 и TIOB6 относятся к каналу счётчика таймера 6. Имеется три модуля счётчиков таймера (TC0, TC1, TC2). Каждый из них имеет три канала, что делает канал с индексом 6 (из всех каналов модуля) каналом с индексом 0 модуля TC2 (2 * 3 + 0 == 6).

Модуль TC2 можно настроить на сброс счетчика в ноль и начало отсчета при поступлении фронта сигнала TIOB6. И передавать значение счётчика во второй регистр (RA) по фронту сигнала на TIOA6. Оттуда можно извлечь и интерпретировать сохранённое значение счётчика в коде.

Таймер также можно останавливать автоматически. Я этого не делал. Счётчик обнулится примерно через 102 секунды — именно столько составляет 32-битный счётчик с частотой 42 МГц. Я бы добавил автоостановку, но это не было необходимостью демонстрировать основную идею, а я хотел показать вам код, пока я не забыл.

Если вы запустите код, он просто будет непрерывно выводить наибольшую разницу во времени между нарастающим фронтом на выводе 4 и следующим нарастающим фронтом на выводе 5.

Надеюсь, этот код послужит вам отправной точкой. Чтобы полностью разобраться в этом, вам, вероятно, придётся прочитать техническое описание микросхемы и изучить файлы CMSIS. А именно:

  • system/CMSIS/Device/ATMEL/sam3xa/include/sam3x8e.h
  • system/CMSIS/Device/ATMEL/sam3xa/include/component/component_tc.h
static const auto TIMER_TICKS_PER_SECOND      = 42.0e6;
static const auto SECONDS_PER_TIMER_TICK      = 1.0 / TIMER_TICKS_PER_SECOND;
static const auto NANO_SECONDS_PER_TIMER_TICK = SECONDS_PER_TIMER_TICK * 1.0e9;


void configure_tc2_for_measurement() {
  PMC->PMC_PCER1 = PMC_PCER1_PID33;  // использовать менеджер питания
                                     // для включения канала счетчика таймера 6 (подходит для TIOB6/TIOA6)
                                     // он же TC2 Канал 0 (TC0/1/2 все имеют по три канала.
                                     // канал 6 — нулевой канал TC2)
                                     // которое является периферийным устройством с идентификационным номером 33.
  TC2->TC_CHANNEL[0].TC_CCR  = TC_CCR_CLKEN; // подключить TC2 к периферийному тактовому сигналу
  TC2->TC_CHANNEL[0].TC_CMR  =  
      TC_CMR_TCCLKS_TIMER_CLOCK1 // MCLK / 2 == 42 МГц тактовая частота таймера (максимально доступная)
    | TC_CMR_ETRGEDG_RISING      // сбросить и запустить счетчик
                                 // по переднему фронту TIOB6/PC26/Arduino-Pin-5
    | TC_CMR_LDRA_RISING         // захват значения счетчика на TC2->CHANNEL[0].TC_RA по переднему фронту TIOA6/PC25/Ardunio-Pin-5
    ;
}


void setup() {
  Serial.begin(9600);

  pinMode(4, INPUT_PULLUP); // ранний нарастающий сигнал | GPIO PC26 | сигнал TIOB6
  pinMode(5, INPUT_PULLUP); // поздний нарастающий сигнал | GPIO PC25 | сигнал TIOA6

  configure_tc2_for_measurement();
}


uint32_t most_recent_measurement_as_42mhz_ticks() {
  return TC2->TC_CHANNEL[0].TC_RA;
}

double tick_count_as_nanoseconds(uint32_t ra) {
  return most_recent_measurement_as_42mhz_ticks()
         * NANO_SECONDS_PER_TIMER_TICK;
}

double most_recent_measurement_as_nanoseconds() {
  return tick_count_as_nanoseconds(
    most_recent_measurement_as_42mhz_ticks()
  );
}

void loop() {
  Serial.print("nanoseconds:  ");
  Serial.print(most_recent_measurement_as_nanoseconds());
  Serial.print(" // примерно с точностью ");
  Serial.print(NANO_SECONDS_PER_TIMER_TICK);
  Serial.print(" nanoseconds");
  Serial.println();
}
,

@thebusybee Ты права. Наверное, я перепечатал, а потом скопировал. Не знаю. В любом случае, спасибо., @timemage