Как прочитать направление таймера в фазе правильного ШИМ?

Мне пришлось использовать Timer0 с корректным по фазе ШИМ и прескалером 1. Это приводит к частоте ШИМ 32 кГц и, очевидно, влияет на миллисы, задержки и микросы. (Timer1 и Timer 2 также используются, поэтому переход на них невозможен)

millis() тривиально обойти - умножить на 32, так как обработчик прерывания TOV0 вызывается в 32 раза чаще (при условии переполнения, конечно).

micros() несколько сложнее - как обычно, это счетчик переполнения Timer0 (как используется в millis (), хотя в данном случае он сдвинут) плюс текущее значение TCNT0. Это отлично работает, когда TCNT0 только увеличивается и сбрасывается до 0 после МАКСИМУМА, но в фазе правильного ШИМ он может идти вверх или вниз.

Я думаю, что мне нужно найти направление, которое в данный момент использует Таймер, но, переливаясь через таблицу данных для ATmega328P, я не могу найти способ сделать это...

Есть ли способ прочитать это, например, из реестра, который я могу опросить?

В качестве альтернативы, может ли кто - нибудь еще придумать способ получить микросекундное (или близкое-10 микросекунд было бы достаточно хорошо) разрешение, когда все таймеры находятся в фазокорректном режиме?

Я рассматриваю возможность повторного чтения TCNT0 дважды и получения его из этого-с прескалером 1I я предполагаю, что невозможно прочитать его в два раза быстрее, чем он может измениться - однако я бы предпочел более аккуратное решение, если оно есть.

, 👍2

Обсуждение

Я боюсь, что это не просто, или возможно вообще. Поскольку каждый код для проверки направления занимает несколько часов, вам нужно посчитать их и принять во внимание. Кроме того, если вы используете прерывания, вам нужно будет отключить их в этой критической части. -- Вам действительно нужен micros()?, @the busybee

Ваше запасное решение-лучшее, и ваше предположение верно: с прескалером на единице вы не можете считывать счетчик в два раза быстрее, чем он меняется., @Edgar Bonet

Может быть, вы можете использовать регистр "OC0x". Сейчас у меня нет под рукой atmega328p, но вы можете проверить его. Соответствующая часть в таблице данных: "В неинвертирующем режиме сравнения выходных данных значение output compare (OC0x) очищается при сопоставлении TCNT0 и OCR0x при подсчете вверх и устанавливается при сопоставлении вниз. В режиме инвертирования выходного сравнения операция инвертируется. (заголовок 14.7.4 Корректный по фазе ШИМ-режим"), @Swedgin

Используйте флаг, чтобы следить за ним, @Swedgin

Вы можете обойти эту проблему и использовать [_delay_us](https://www.nongnu.org/avr-libc/user-manual/group__util__delay.html) вместо этого. Единственным недостатком является то, что _delay_us не учитывает время, затрачиваемое внутри ISR, так как он просто выполняет кучу инструкций NOP., @Gerben

@Swedgin, этот вопрос был задан на другом сайте stackexchange, и у меня была та же мысль. Или то же самое, насколько я могу судить. Вчера я экспериментировал с этой идеей, за исключением использования timer1 с прескалером 1024, потому что это дает около 4 секунд на колебание от одного конца диапазона до другого. По какой-то причине он, похоже, не устанавливал флаги прерывания сравнения вывода. Но меня прервало кое-что еще, прежде чем я понял, почему., @timemage


1 ответ


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

1

Я решил пойти с методом inspect и соответствующим образом модифицировал wiring.c - возможно, не подходит для каждого варианта использования, но решает мою проблему (наряду с парой настроек, изменяющих #define на volatile переменные и добавляющих сеттер для них.

микрос обновлен до:

unsigned long micros() {
  unsigned long m;
  uint8_t oldSREG = SREG, t1, t2;
  
  cli();
  m = timer0_overflow_count;
#if defined(TCNT0)
  t1 = TCNT0;
  t2 = TCNT0;
#elif defined(TCNT0L)
  t1 = TCNT0L;
  t2 = TCNT0L;
#else
  #error TIMER 0 not defined
#endif

#ifdef TIFR0
  if ((TIFR0 & _BV(TOV0)) && (t1 == timer0_phase_correct ? 0 : 255))
    m++;
#else
  if ((TIFR & _BV(TOV0)) && (t1 == timer0_phase_correct ? 0 : 255))
    m++;
#endif

  SREG = oldSREG;
  
  if (timer0_phase_correct && t1 > t2){
    t2 = 510 - t2;
  }
  
  return (m * us_per_timer0_overflow) + (clockCyclesToMicroseconds(timer0_prescale * t2));
}
,

Примечание: это не сработает только с этим изменением, но изменение проводки.c - это не та тема, которую я хочу обсуждать, и любой, кто способен на это, может легко сделать вывод о других изменениях из того, что я изложил выше. Здесь будут драконы., @Simm

Где определен timer0_phase_correct?, @user172650