Отрегулируйте расчет времени после изменения частоты 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);
}
@Splitframe, 👍7
Обсуждение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
Вы установили MICROSECONDS_PER_TIMER0_OVERFLOW
правильное значение, однако оно используется только MILLIS_INC
, который, в свою очередь, используется только millis()
. Это означает, что другие функции синхронизации, такие как micros()
, delay()
, delayMicroseconds()
, прерываются при изменении timer0. Это своего рода ошибка, и она может быть исправлена в будущей версии, но на данный момент библиотеки Arduino ожидают, что вы оставите timer0 в покое. Лучший обходной путь — использовать millis()
только для критических по времени функций.
ЭТО НЕПОЛНЫЙ ОТВЕТ. Эдгар знает, о чем говорит, послушайте его
Сейчас я работаю над тем же, но на 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
Благодаря ответу Эдгара и объяснению Небси о том, как именно работает счетчик, я смог придумать свою собственную реализацию того, как исправить задержку и 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; // сброс истек
}
}
}
- Использование millis() и micros() внутри процедуры прерывания
- Кнопка с таймером переключения и функцией сброса времени + светодиод обратной связи
- Использовать timer0, не влияя на millis() и micros().
- Можно ли сгенерировать точный тактовый импульс 15 кГц с помощью ардуино?
- Генерация сигнала частотой 38 кГц без таймеров
- Сброс Arduino с помощью ПО (каждый день)
- Как отслеживать миллисекунды в спящем режиме
- Светодиод Arduino PWM с замиранием в сборке
Все ваши настройки верны. Мое лучшее предположение, если вы, возможно, изменили неправильную проводку.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