Отрегулируйте расчет времени после изменения частоты Timer0

У меня есть Arduino Nano с 328P, и мне нужны все 6 контактов ШИМ.

Поэтому мне пришлось настроить предварительный делитель и режим WGM Timer0.

Теперь он находится в фазово-корректном режиме ШИМ с предварительным делителем 1.

TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
TCCR0B = _BV(CS00);

Теперь мне нужен расчет времени работы для других библиотек, но так как У Timer0 была такая обязанность, теперь все не в порядке.

Я попытался отрегулировать проводку.c

// прескалер настроен так, что timer0 тикает каждые 64 такта, а
// обработчик переполнения вызывается каждые 256 тиков.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

к этому

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 510))

Но я как будто ничего не менял. (проверил другие настройки, которые были изменены, поэтому он был скомпилирован заново)

Весь код:

void setup() {

  // Установить таймер 0, 1 и 2
  // Регистр A: выход A и B в неинвертированном ШИМ и режиме ШИМ для правильной фазы.
  // Регистр B: Pre Scaler в 1.
  TCCR0A = _BV(COM0A1) | _BV(COM0B1) | _BV(WGM00);
  TCCR0B = _BV(CS00);

  TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10);
  TCCR1B = _BV(CS10);

  TCCR2A = _BV(COM2A1) | _BV(COM2B1) | _BV(WGM20);
  TCCR2B = _BV(CS20);

  pinMode(8, OUTPUT);


}
void loop() {

  digitalWrite(8, LOW);
  delay(65000);
  digitalWrite(8, HIGH);
  delay(65000);

}

, 👍7

Обсуждение

Все ваши настройки верны. Мое лучшее предположение, если вы, возможно, изменили неправильную проводку.c. Лучший способ диагностировать это — перейти в «Файл»> «Настройки» и установить флажок компиляции в разделе «Показать подробный вывод во время». Затем нажмите «Подтвердить», скопируйте вывод в текстовый редактор и найдите «wire.c» и посмотрите, откуда он его извлекает, затем откройте файл и убедитесь, что ваши изменения там., @Jake C

Когда я очищаю папку tmp и заново проверяю, он показывает: [Это.](http://pastebin.com/377cEkLB) И это тоже проводка.c я исправил :(, @Splitframe

Я не знаю, что сказать, во что бы то ни стало, это должно работать. Другой вариант — просто масштабировать время [самостоятельно](http://forum.arduino.cc/index.php?topic=16612.msg121040#msg121040). Вы можете обернуть это в макрос типа #define SCALE_UP(x) x<<6, а затем использовать его как delay(SCALE_UP(1000)), @Jake C

Это странно, реальное значение кажется действительно произвольным. Я попробовал два nano и один Uno, оба имеют одинаковые результаты. И когда я пытаюсь вычислить значение задержки, я получаю 64000, но когда я вставьте, что это всего лишь 800 мс вместо 1000 мс. В целях тестирования я уже выбросил все #includes, это просто изменение регистра и цифровая запись и задержка., @Splitframe

Как вы измеряете? У вас случайно нет осциллографа?, @Jake C

Нет, у меня его нет. У меня есть светодиод, который мигает кодом выше, и теперь я добавил a Последовательный выход в Arduino IDE. Когда я ставлю задержку в 200000, я получаю ок. 1050 мс, @Splitframe

О, я только что заметил... когда я могу использовать millis() для измерения, ну, милли, и они кажутся правильными, это означает, что внутренний хронометраж в порядке. Просто задержка, кажется, имеет ошибку. _вздох_ В любом случае. Спасибо за помощь Джейку!, @Splitframe

А, это имеет смысл, учитывая то, как эти функции настроены в wireing.c. Можете ли вы попробовать что-нибудь для меня, попробуйте запустить delayMicroseconds(1000000); и дайте мне знать, если это более точно., @Jake C

Кроме того, какую версию Arduino вы используете?, @Jake C

Похоже, что в delayMicroseconds() тоже есть ошибка. это даже не вызывает задержки мс., @Splitframe

Странно, delayMicroseconds() использует другой механизм, чем delay(), поэтому мне было интересно, не повлияет ли это на него. Я опубликовал ответ, резюмирующий наши выводы., @Jake C

Похоже, что в delayMicroseconds() тоже есть ошибка. это даже не вызывает задержки в мс. - Пожалуйста, отправьте код, который поддерживает это утверждение., @Nick Gammon

В приведенном выше коде просто замените delay(65000) на delayMicroseconds(). Я установил значение «16000000L», потому что у меня процессор 16 МГц. Светодиод просто выглядит тусклым, но не выключается и не включается каждую секунду. Если сравнить его с простым millis() - startMillis, задержка составит 5 мс с 16000000L и 9 мс с 128000000., @Splitframe


4 ответа


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

4

Исправление функций хронометража с помощью настроек ШИМ не так простой. Вы должны хотя бы попытаться переписать ISR(TIMER0_OVF_vect), micros() и, возможно, delay(). Вот почему:

Во-первых, существует проблема округления. Время хранится с использованием двух глобальных переменные:

volatile unsigned long timer0_millis;
static unsigned char timer0_fract;

Первое — это то, что возвращает millis(). Второй следит за сколько времени прошло с последней полной миллисекунды, и это так в единицах 8 мкс. Две переменные увеличиваются на ISR(TIMER0_OVF_vect) примерно так:

m += MILLIS_INC;  // временная копия timer0_millis
f += FRACT_INC;   // временная копия timer0_fract

В обычной конфигурации Uno ISR вызывается каждые 1024 мкс. Тогда MILLIS_INC равно 1, а FRACT_INC равно 3. С вашим таймером конфигурации, ISR вызывается каждые 31,875 мкс (510 циклов), затем MILLIS_INC должен быть равен 0, а FRACT_INC должен быть равен 3,984375. Но с тех пор мы имеем дело с целыми числами, оно будет округлено до 3, и ваш millis() будет тикать примерно на 25% медленнее.

Простым решением будет

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512))

для того, чтобы FRACT_INC был равен 4, а millis() был на 0,4% быстрее. Или вы можете сделать timer0_fract 16-битной переменной и заставить ее считать часы циклы, просто чтобы избежать этой ошибки. Любой вариант должен исправить millis(), но у вас все еще есть проблема с micros().

micros() работает, считывая как timer0_overflow_count (увеличенный на 1 в ISR) и фактическое значение счетчика. Поскольку ваш счетчик сейчас двигаясь попеременно вверх и вниз, будет сложнее вычислить отсчет микросекунд от этих показаний. Может быть, вы могли бы взять два последовательные показания счетчика, просто чтобы знать, идет ли он вверх или вниз...

А еще есть delay(), который зависит от micros(). Если вы исправите micros(), delay() должны работать нормально. Если нет, вы можете переписать delay() вместо этого использовать millis(), что должно быть легко, но вы потерять некоторую точность.

,

Спасибо за развернутый ответ! На данный момент также спасибо @jakec за обсуждение проблемы со мной!, @Splitframe

Поскольку ваш счетчик теперь попеременно идет вверх и вниз Почему TCNT0 идет вверх или вниз? Он растет, и каждый раз, когда он переполняется, вызывается ISR. Я что-то пропустил? Я попытался решить эту проблему, увеличив timer0_overflow_count только один раз каждые 64 раза, а в функции micros() вернуть время как return ((m << 8) + (t % 64)) * (64/ clockCyclesPerMicrosecond()); Итак, я модифицирую счетчик TCNT0, так как мой таймер тикает в 64 раза быстрее. Но хотя micros() кажется в порядке, delay() по-прежнему работает быстрее, и я не понимаю, почему..., @Lefteris

@Lefteris: Счетчик движется вверх и вниз, потому что OP настроил его в режиме «фазокорректной ШИМ»., @Edgar Bonet

Исправление вышеизложенного, модуль был неверным, теперь я делю на 64 функцию возврата: return ((m << 8) + (t/64)) * (64 / clockCyclesPerMicrosecond()); Все еще проблема остается с функцией задержки, которую я не понимаю, @Lefteris

@EdgarBonet Если я правильно понял, регистр TCTN0 увеличивается с каждым тактом CLK/64. Так что это только вверх. Он сбрасывается каждый раз, когда он переполняется и вызывается ISR TIMER0_OVF_vect. Так что до сих пор не понимаю, какое отношение к этому имеет фазовая правильная ШИМ., @Lefteris

@Lefteris: прочитайте [техническое описание](http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-42735-8-bit-AVR-Microcontroller-ATmega328-328P_Datasheet.pdf#page=134): « Режим Phase Correct PWM основан на работе с двумя наклонами: счетчик постоянно считает от НИЗА к ВЕРХУ, а затем от ВЕРХА к НИЗУ»., @Edgar Bonet

@EdgarBonet Да, я только что прочитал это. Спасибо, @Lefteris


0

Вы установили MICROSECONDS_PER_TIMER0_OVERFLOW правильное значение, однако оно используется только MILLIS_INC, который, в свою очередь, используется только millis() . Это означает, что другие функции синхронизации, такие как micros(), delay(), delayMicroseconds(), прерываются при изменении timer0. Это своего рода ошибка, и она может быть исправлена в будущей версии, но на данный момент библиотеки Arduino ожидают, что вы оставите timer0 в покое. Лучший обходной путь — использовать millis() только для критических по времени функций.

,

0

ЭТО НЕПОЛНЫЙ ОТВЕТ. Эдгар знает, о чем говорит, послушайте его

Сейчас я работаю над тем же, но на ATMega2560. Этот веб-сайт помог мне лучше понять функцию millis() . Таймер переполняется каждые 510 отсчетов (см. техническое описание, стр. 123 для atmega2560). Я считаю, что это 510, а не 512, потому что он считает вверх и вниз, но не повторяет счет вверху или внизу - например, если вы считаете, начиная с 1 до 10, а затем обратно (без повторения 10), у вас будет подсчитано 19 раз, а не 20. Этот счетчик начинается с 0, считает до 255, а затем возвращается к 0, вызывая переполнение, прежде чем снова достигнет 0. Это 256 (счет вверх, включая 0 и 255) + 254 (счет вниз, не включая 255 или 0) = 510. Я написал скрипт на Python, чтобы визуализировать эффект этих изменений и попытаться их учесть. Это считается до 10 миллионов, поэтому для запуска требуется время, но к концу, даже с масштабированием, ошибка, если просто настроить millis() после факта, может составлять ~ 7 секунд.

#! /usr/bin/окружение Python
импортировать numpy как np

realT = np.zeros (10000000)
timer0_millis = np.zeros (10000000)
timer0_fract = np.zeros (10000000)
я=0

для i в диапазоне (1 10000000):
timer0_millis[i] = timer0_millis[i-1] + 1
timer0_fract[i] = timer0_fract[i-1] + 3
реальное Т [я] = я * 31875
если timer0_fract[i-1] >= 125:
timer0_fract[i] = timer0_fract[i-1] - 125
timer0_millis[i] = timer0_millis[i-1] + 1

скорректировано = timer0_millis*510/(256*64)

print "после 100 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[99], realT[99]/1000000, скорректировано[99])
print "после 1000 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[999], realT[999]/1000000, скорректировано[999])
print "после 10000 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[9999], realT[9999]/1000000, скорректировано[9999])
print "после 100000 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[99999], realT[99999]/1000000, скорректировано[99999])
print "после 1000000 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[999999], realT[999999]/1000000, скорректировано[999999])
print "после 10000000 прерываний таймера millis() будет читать %d, фактическое значение millis равно %.6f, скорректированное значение millis равно %.6f" % (timer0_millis[9999999], realT[9999999]/1000000, скорректировано[9999999])
,

Вы написали: «_Я считаю, что в режиме корректной фазы таймер переполняется каждые 510 отсчетов, а не 512, как указывает Эдгар_». Пожалуйста, прочитайте мой ответ еще раз и не цитируйте меня неправильно., @Edgar Bonet

Извините, я имел в виду эту строку «#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds (1 * 512))» — я обновил свой комментарий, поправьте меня, если я ошибаюсь, я не собирался неправильно цитировать, @Nerbsie

Есть _очень веская причина_ для того, чтобы в этой строке было 512 вместо 510. Прочитай мой ответ и поймешь., @Edgar Bonet

Итак, вы добавляете два счетчика таймера, каждый из которых равен 62,5 нс, чтобы учесть ошибку, вызванную принудительным округлением?, @Nerbsie

В моем приложении я не мог изменить wire.c, поэтому в качестве быстрого исправления я вставил коэффициент для масштабирования относительных значений, возвращаемых из millis(). С учетом сказанного я пытался смоделировать, как выглядели мои изменения по сравнению с правильным исправлением, которым я считаю это. Кроме того, имеет ли значение эта ошибка, когда millis() используется для относительной синхронизации, например, now=millis() и while(millis() - now < 100) (т.е. это постоянная ошибка или меняется ли оно; можно ли его использовать для масштабирования условия)?, @Nerbsie

Да. С 510 округление делает millis() на 25% медленнее, тогда как 512 делает это на 0,4% быстрее. Что касается вашего второго вопроса, вы можете попытаться сформулировать его как правильный вопрос и показать в коде, как именно вы «вставили коэффициент»., @Edgar Bonet

Если это на 25% медленнее, может ли это объяснить ошибку: now=millis(), а затем while(millis() -now < x*100) с x=4/3? Извините, это мой первый пост на этом форуме, и я думал, что включил некоторую полезную информацию, но я думаю, что я должен оставить ответы экспертам., @Nerbsie

Эдгар, если вы хотите продолжать помогать мне, я решил, что вместо того, чтобы отвечать на вопрос своими вопросами и потенциальной дезинформацией, я [задам вопрос](http://arduinoprosto.ru/q/30167/help- управление-выпадением-от-режима-таймера-0-и-пределирования-на-atmega2560-wi), @Nerbsie


0

Благодаря ответу Эдгара и объяснению Небси о том, как именно работает счетчик, я смог придумать свою собственную реализацию того, как исправить задержку и micros(), которая пока что, кажется, отлично работает на моем Arduino Uno.

>

Я не говорю, что это самая точная реализация, особенно я сомневаюсь в micros(), если, например, происходит переполнение таймера между чтением t1 и t2, но пока что для меня это работает хорошо.

Во-первых, по рекомендации Эдгара я определил:

#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(1 * 512)) 

Однако это не влияет на синхронизацию micros() или delay().

Функция micros() была изменена на чтение два раза из TCNT0 — чтобы определить, идет ли счет вверх или вниз. Это делается с помощью предложения «if (t1 > t2)». Счетчик переполнения "m" умножается на 510, так как счетчик истекает после 510 шагов. Затем к этому прибавляется рассчитанное значение счетчика «t», деленное на количество тактов в микросекунде. (Примечание: Prescaler = 1, поэтому дальнейшее умножение не требуется).

unsigned long micros() {
    unsigned long m;
    uint8_t oldSREG = SREG, t1, t2;
    uint16_t t;

    cli();
    m = timer0_overflow_count;
#if defined(TCNT0)
    t1 = TCNT0;
#elif defined(TCNT0L)
    t1 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

#if defined(TCNT0)
    t2 = TCNT0;
#elif defined(TCNT0L)
    t2 = TCNT0L;
#else
    #error TIMER 0 not defined
#endif

    if (t1 >= t2) {
        t = 510 - t2;       // счетчик работает в обратном направлении
    } else {
        t = t2;             // счетчик работает вверх
    }       

#ifdef TIFR0
    if ((TIFR0 & _BV(TOV0)) && (t2 > 1))        // если установлен флаг переполнения -> увеличить м
        m++;
#else
    if ((TIFR & _BV(TOV0)) && (t2 > 1))
        m++;
#endif

    SREG = oldSREG;

    return ((m * 510) + t)  / clockCyclesPerMicrosecond();          
}

ОДНАКО - похоже, это работает нормально, но у меня были проблемы с самого начала (перед редактированием этого снова). Из-за того, что счетчик работает намного быстрее, каждые 268 секунд беззнаковый длинный тип данных micros() переполняется и снова начинается с нуля. Это приводило к нежелательной блокировке delay(), особенно если длительные задержки, как в моем случае, используются задержки в одну секунду на итерации цикла. Поэтому мне также пришлось добавить обнаружение переполнения в функцию delay() в arduino wired.c.

Если происходит переполнение, время может быть неточным. Но так как это слишком 268 секунд, это может быть приемлемо. Пока что с этим изменением функция задержки снова работает нормально на моей стороне.

void delay(unsigned long ms)
{
    uint32_t start = micros();
    uint32_t elapsed = 0;   

    while (ms > 0) {
        yield();
        while ( ms > 0 && (micros() - 1000) >= (start + elapsed)) {         
            ms--;
            elapsed += 1000;            
        }
        if( start > micros() ) {    // обнаружено переполнение
            start = micros();       // сбросить начало
            elapsed = 0;            // сброс истек
        }
    }
}
,