Перемещение функций Wiring.c с таймера 0 на таймер 1.
Я использую Arduino Leonardo (ATmega32U4), и мне нужно использовать timer0 для ШИМ. Поскольку по умолчанию в файле Wiring.c функции micros()
и millis()
используют timer0, я решил, что, поскольку timer1 не используется, я мог бы установить уточните timer1, как настроен timer0 для функций micros()
и millis()
, и измените их код так, чтобы он использовал timer1 вместо timer0.
Думаю, теоретически это должно сработать. Я прочитал таблицу данных, и таймер 1, будучи 16-битным таймером, может работать в 8-битном быстром режиме ШИМ (для чего настроен таймер 0). Поэтому я модифицировал файл Wiring.c так, как указано выше: настроил прескалер так, чтобы он соответствовал оригиналу для таймера 0 (f/64), и перевел таймер 1 в 8-битный режим быстрой ШИМ.
Я изменил все функции в файле Wiring.c, которые использовали любой регистр таймера 0, и изменил их так, чтобы они использовали соответствующие регистры таймера 1.
Однако проблема в том, что вроде бы я все сделал правильно, но когда я пытаюсь запустить простую программу, которая вызывает Serial.println("hello world")
, она портится. Вместо этого выходные данные отображают повторяющийся символ «h» на последовательной консоли. Я не совсем понимаю, в чем дело, и интересно, может ли кто-нибудь помочь.
Вот мой переписанный файл Wiring.c, который по существу переносит все функции с таймера 0 на таймер 1:
#include "wiring_private.h"
// прескалер настроен так, что таймер 0 срабатывает каждые 64 такта,
// и обработчик переполнения вызывается каждые 256 тактов.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))
// целое количество миллисекунд на переполнение таймера 0
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)
// дробное количество миллисекунд на переполнение таймера 0. мы сдвигаем вправо
// на три, чтобы уместить эти числа в байт. (для тактовой частоты мы заботимся
// около - 8 и 16 МГц - точность не теряет.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)
volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;
#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
ISR(TIM0_OVF_vect)
#else
/*************************** Modified wiring.c TIMER1 ISR ***********************/
ISR(TIMER1_OVF_vect)
#endif
{
// копируем их в локальные переменные, чтобы их можно было сохранить в регистрах
// (летучие переменные должны считываться из памяти при каждом доступе)
unsigned long m = timer0_millis;
unsigned char f = timer0_fract;
m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
f -= FRACT_MAX;
m += 1;
}
timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;
}
/************************** End TIMER1 ISR **************************************/
unsigned long millis() {
unsigned long m;
uint8_t oldSREG = SREG;
// отключаем прерывания, пока мы читаем timer0_millis, иначе мы можем получить
// противоречивое значение (например, в середине записи в timer0_millis)
cli();
m = timer0_millis;
SREG = oldSREG;
return m;
}
/************************ Modified micros() function ****************************/
unsigned long micros() {
unsigned long m;
uint8_t oldSREG = SREG, t;
cli();
m = timer0_overflow_count;
#if defined(TCNT1)
t = TCNT1;
#else
#error TIMER 1 not defined
#endif
#ifdef TIFR1
if ((TIFR1 & _BV(TOV1)) && (t < 255))
m++;
#else
#error TIFR 1 not defined
#endif
SREG = oldSREG;
return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}
/************************ End micros() ******************************************/
void delay(unsigned long ms) {
uint32_t start = micros();
while (ms > 0) {
yield();
while ( ms > 0 && (micros() - start) >= 1000) {
ms--;
start += 1000;
}
}
}
/* Delay for the given number of microseconds. Assumes a 1, 8, 12, 16, 20 or 24 MHz clock. */
void delayMicroseconds(unsigned int us) {
// вызов = 4 цикла + от 2 до 4 циклов на инициализацию (2 для постоянной задержки, 4 для переменной)
// вызов функции avrliblay_u s() с низкими значениями (например, 1 или
// 2 микросекунды) дает задержки дольше, чем хотелось бы.
//delay_us(нас);
#if F_CPU >= 24000000L
// для тактовой частоты 24 МГц для любителей приключений, пытающихся разогнаться
// исправление нулевой задержки
if (!us) return; // = 3 цикла (4, если true)
// следующий цикл занимает 1/6 микросекунды (4 цикла)
// за итерацию, поэтому выполняем ее шесть раз за каждую микросекунду
// запрошена задержка.
us *= 6; // x6 us, = 7 тактов
// учитываем время, затраченное на предыдущие команды.
// мы только что сожгли 22 (24) цикла выше, удалим 5, (5*4=20)
// нас не менее 6, поэтому мы можем вычесть 5
us -= 5; //=2 цикла
#elif F_CPU >= 20000000L
// для тактовой частоты 20 МГц на редких платах Arduino
// для задержки в одну микросекунду просто возвращаемся. накладные расходы
// вызов функции занимает 18 (20) тактов, что составляет 1 мкс
__asm__ __volatile__ (
"nop" "\n\t"
"nop" "\n\t"
"nop" "\n\t"
"nop"); //просто ждём 4 цикла
if (us <= 1) return; // = 3 цикла (4, если true)
// следующий цикл занимает 1/5 микросекунды (4 цикла)
// за итерацию, поэтому выполняем ее пять раз за каждую микросекунду
// запрошена задержка.
us = (us << 2) + us; // x5 us, = 7 тактов
// учитываем время, затраченное на предыдущие команды.
// мы только что сожгли 26 (28) циклов выше, удалим 7, (7*4=28)
// нас не менее 10, поэтому мы можем вычесть 7
us -= 7; // 2 цикла
#elif F_CPU >= 16000000L
// для тактовой частоты 16 МГц на большинстве плат Arduino
// для задержки в одну микросекунду просто возвращаемся. накладные расходы
// вызов функции занимает 14 (16) тактов, что составляет 1 мкс
if (us <= 1) return; // = 3 цикла (4, если true)
// следующий цикл занимает 1/4 микросекунды (4 цикла)
// за итерацию, поэтому выполняем ее четыре раза за каждую микросекунду
// запрошена задержка.
us <<= 2; // x4 us, = 4 цикла
// учитываем время, затраченное на выполнение предыдущих команд.
// мы только что сожгли 19 (21) циклов выше, удалим 5, (5*4=20)
// нас не менее 8, поэтому мы можем вычесть 5
us -= 5; // = 2 цикла,
#elif F_CPU >= 12000000L
// для тактовой частоты 12 МГц, если кто-то работает с USB
// для задержки в 1 микросекунду просто возвращаемся. накладные расходы
// вызов функции занимает 14 (16) тактов, что составляет 1,5 мкс
if (us <= 1) return; // = 3 цикла (4, если true)
// следующий цикл занимает 1/3 микросекунды (4 цикла)
// за итерацию, поэтому выполняем ее три раза для каждой микросекунды
// запрошена задержка.
us = (us << 1) + us; // x3 us, = 5 тактов
// учитываем время, затраченное на предыдущие команды.
// мы только что сожгли 20 (22) циклов выше, удалим 5, (5*4=20)
// нас не менее 6, поэтому мы можем вычесть 5
us -= 5; //2 цикла
#elif F_CPU >= 8000000L
// для внутренних часов 8 МГц
// для задержки в 1 и 2 микросекунды просто возвращаемся. накладные расходы
// вызов функции занимает 14 (16) тактов, это 2us
if (us <= 2) return; // = 3 цикла (4, если true)
// следующий цикл занимает 1/2 микросекунды (4 цикла)
// за итерацию, поэтому выполняем ее дважды за каждую микросекунду
// запрошена задержка.
us <<= 1; //x2 нас, = 2 цикла
// учитываем время, затраченное на предыдущие команды.
// мы только что сожгли 17 (19) циклов выше, удалим 4, (4*4=16)
// нас не менее 6, поэтому мы можем вычесть 4
us -= 4; // = 2 цикла
#else
// для внутренней тактовой частоты 1 МГц (настройки по умолчанию для распространенных микроконтроллеров Atmega)
// накладные расходы на вызовы функций составляют 14 (16) тактов
if (us <= 16) return; //= 3 цикла (4, если true)
if (us <= 25) return; //= 3 цикла (4, если true), (должно быть не менее 25, если мы хотим вычесть 22)
// компенсируем время выполнения предыдущей и следующей команд (около 22 тактов)
us -= 22; // = 2 цикла
// следующий цикл занимает 4 микросекунды (4 цикла)
// на итерацию, поэтому выполните его us/4 раза
// нас как минимум 4, деление на 4 дает 1 (нет ошибки с нулевой задержкой)
us >>= 2; // используем div 4, = 4 цикла
#endif
// занято ожидание
__asm__ __volatile__ (
"1: sbiw %0,1" "\n\t" // 2 цикла
"brne 1b" : "=w" (us) : "0" (us) // 2 цикла
);
// возврат = 4 цикла
}
void init() {
// это необходимо вызвать перед setup(), иначе некоторые функции не будут
// работать там
sei();
// на ATmega168 таймер 0 также используется для быстрой аппаратной ШИМ
// (использование ШИМ с фазовой коррекцией означало бы, что таймер 0 переполнялся вдвое реже
// что приводит к разному поведению millis() на ATmega8 и ATmega168)
#if defined(TCCR0A) && defined(WGM01)
sbi(TCCR0A, WGM01);
sbi(TCCR0A, WGM00);
#endif
// устанавливаем коэффициент предварительного масштабирования таймера 0 на 64
#if defined(__AVR_ATmega128__)
// Зависит от процессора: разные значения для ATmega128
sbi(TCCR0, CS02);
#elif defined(TCCR0) && defined(CS01) && defined(CS00)
// эта комбинация для стандартного atmega8
sbi(TCCR0, CS01);
sbi(TCCR0, CS00);
#elif defined(TCCR0B) && defined(CS01) && defined(CS00)
// эта комбинация для стандартных 168/328/1280/2560
sbi(TCCR0B, CS01);
sbi(TCCR0B, CS00);
#elif defined(TCCR0A) && defined(CS01) && defined(CS00)
// эта комбинация предназначена для серии __AVR_ATmega645__
sbi(TCCR0A, CS01);
sbi(TCCR0A, CS00);
#else
#error Timer 0 prescale factor 64 not set correctly
#endif
// включаем прерывание переполнения таймера 0
#if defined(TIMSK) && defined(TOIE0)
sbi(TIMSK, TOIE0);
#elif defined(TIMSK0) && defined(TOIE0)
sbi(TIMSK0, TOIE0);
#else
#error Timer 0 overflow interrupt not set correctly
#endif
// таймеры 1 и 2 используются для аппаратной ШИМ с фазовой коррекцией
// это лучше для двигателей, поскольку обеспечивает равномерную форму сигнала
// обратите внимание, однако, что в режиме быстрого ШИМ можно достичь частоты выше
// 8 МГц (с тактовой частотой 16 МГц) при рабочем цикле 50 %
/******************** Modified Code for Timer 1 *********************************/
#if defined(TCCR1B) && defined(CS11) && defined(WGM10)
// устанавливаем коэффициент предварительного масштабирования таймера 1 на 64
sbi(TCCR1B, CS11);
sbi(TCCR1B, CS10);
// переводим таймер 1 в 8-битный режим быстрой ШИМ
sbi(TCCR1B, WGM12);
sbi(TCCR1A, WGM10);
// включаем прерывание по переполнению таймера 1
sbi(TIMSK1, TOIE1);
#endif
/********************* End Timer 1 **********************************************/
// устанавливаем коэффициент предварительного масштабирования таймера 2 на 64
#if defined(TCCR2) && defined(CS22)
sbi(TCCR2, CS22);
#elif defined(TCCR2B) && defined(CS22)
sbi(TCCR2B, CS22);
//#еще
// Таймер 2 не завершен (может отсутствовать на этом процессоре)
#endif
// настраиваем таймер 2 для ШИМ с фазовой коррекцией (8-бит)
#if defined(TCCR2) && defined(WGM20)
sbi(TCCR2, WGM20);
#elif defined(TCCR2A) && defined(WGM20)
sbi(TCCR2A, WGM20);
//#еще
// Таймер 2 не завершен (может отсутствовать на этом процессоре)
#endif
/************** Modified Code for Timer 3 ***************************************/
#if defined(TCCR3B) && defined(CS31) && defined(WGM30)
// устанавливаем коэффициент предварительного масштабирования таймера 3 на 64
sbi(TCCR3B, CS31);
sbi(TCCR3B, CS30);
// переводим таймер 3 в 8-битный режим быстрой ШИМ
sbi(TCCR3B, WGM32);
sbi(TCCR3A, WGM30);
// включаем прерывание переполнения таймера 3
sbi(TIMSK3, TOIE3);
#endif
/************** End Timer 3 *****************************************************/
#if defined(TCCR4A) && defined(TCCR4B) && defined(TCCR4D) /* beginning of timer4 block for 32U4 and similar */
sbi(TCCR4B, CS42); // устанавливаем коэффициент предварительного масштабирования таймера 4 на 64
sbi(TCCR4B, CS41);
sbi(TCCR4B, CS40);
sbi(TCCR4D, WGM40); // переводим таймер 4 в режим ШИМ с коррекцией по фазе и частоте
sbi(TCCR4A, PWM4A); // включаем режим ШИМ для компаратора OCR4A
sbi(TCCR4C, PWM4D); // включаем режим ШИМ для компаратора OCR4D
#else /* beginning of timer4 block for ATMEGA1280 and ATMEGA2560 */
#if defined(TCCR4B) && defined(CS41) && defined(WGM40)
sbi(TCCR4B, CS41); // устанавливаем коэффициент предварительного масштабирования таймера 4 на 64
sbi(TCCR4B, CS40);
sbi(TCCR4A, WGM40); // переводим таймер 4 в 8-битный режим ШИМ с фазовой коррекцией
#endif
#endif /* end timer4 block for ATMEGA1280/2560 and similar */
#if defined(TCCR5B) && defined(CS51) && defined(WGM50)
sbi(TCCR5B, CS51); // устанавливаем коэффициент предварительного масштабирования таймера 5 на 64
sbi(TCCR5B, CS50);
sbi(TCCR5A, WGM50); // переводим таймер 5 в 8-битный режим ШИМ с фазовой коррекцией
#endif
#if defined(ADCSRA)
// устанавливаем прескалер a2d так, чтобы мы находились в желаемом диапазоне 50–200 кГц.
#if F_CPU >= 16000000 // 16 МГц / 128 = 125 кГц
sbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
#elif F_CPU >= 8000000 // 8 МГц / 64 = 125 кГц
sbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
cbi(ADCSRA, ADPS0);
#elif F_CPU >= 4000000 // 4 МГц / 32 = 125 кГц
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
#elif F_CPU >= 2000000 // 2 МГц / 16 = 125 кГц
sbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
cbi(ADCSRA, ADPS0);
#elif F_CPU >= 1000000 // 1 МГц / 8 = 125 кГц
cbi(ADCSRA, ADPS2);
sbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
#else // 128 кГц / 2 = 64 кГц -> Это самое близкое значение, которое вы можете получить, прескалер равен 2.
cbi(ADCSRA, ADPS2);
cbi(ADCSRA, ADPS1);
sbi(ADCSRA, ADPS0);
#endif
// включаем преобразования a2d
sbi(ADCSRA, ADEN);
#endif
// загрузчик подключает контакты 0 и 1 к USART; отключите их
// здесь, чтобы их можно было использовать как обычный цифровой ввод-вывод; Они будут
// повторное подключение в Serial.begin()
#if defined(UCSRB)
UCSRB = 0;
#elif defined(UCSR0B)
UCSR0B = 0;
#endif
}
@aflyingcougar, 👍4
Обсуждение3 ответа
Я предлагаю оставить код Arduino как есть и написать свои собственные micro() и millis(), используя timer1.
Это должно быть довольно просто.
Хотя я не уверен, что это полностью сделает все ваши изменения правильными, я вижу потенциальную проблему в одной существующей строке кода, которая может быть плохой из-за использования 16-битного таймера вместо 8-битного:
unsigned long micros() {
unsigned long m;
uint8_t oldSREG = SREG, t;
cli();
m = timer0_overflow_count;
#if defined(TCNT1)
t = TCNT1;
#else
#error TIMER 1 not defined
#endif
#ifdef TIFR1
if ((TIFR1 & _BV(TOV1)) && (t < 255))
m++;
#else
#error TIFR 1 not defined
#endif
SREG = oldSREG;
return ((m << 8) + t) * (64 / clockCyclesPerMicrosecond());
}
Здесь t
объявлен как uint8_t
и инициализирован с помощью TCNT1
, который представляет собой uint16_t
(на самом деле это является ссылкой на летучий uint16_t
), это может быть здесь проблемой.
Я бы предложил заменить строку
t = TCNT1
с
t = TCNT1L
где TCNT1L
теперь является ссылкой на Летучий uint8_t
, обращающийся к младшему байте TCNT1.
Для заметки: я по ошибке разместил этот вопрос до того, как создал аккаунт, поэтому не могу выбрать лучший ответ; НО, я нашел решение для всех, кто интересуется.
У меня было включено прерывание более чем для одного таймера (забыл отключить прерывания в коде timer0). Однажды я сделал так, чтобы код сработал! Мне удалось переместить функции millis, micros, Delay и DelayMicros в 16-битный таймер 1!
- TCCR1A и TCCR2A на Леонардо
- Прерывание переполнения Timer0 не работает
- Запуск программного сброса Arduino Leonardo
- Захват ввода с включенным спящим режимом на плате ATM32u4 работает только при каждом втором чтении в спящем режиме.
- Как использовать SPI на Arduino?
- Использование millis() и micros() внутри процедуры прерывания
- Как сделать очень долгую функцию delay(), несколько часов
- Разница между «time_t» и «DateTime»
Я не уверен, что понимаю, почему вы хотите переключиться с таймера 0 на таймер 1, а затем использовать таймер 0 для ШИМ. Почему бы не оставить
wiring.c
как есть и использовать только timer0 для ШИМ?, @jfpoilpretВы написали: «
sbi(TIMSK3, TOIE3); // включаем прерывание переполнения таймера 3
”. Вы реализовалиISR(TIMER3_OVF_vect)
где-нибудь в своей программе?, @Edgar Bonet