Перемещение функций 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
}

, 👍4

Обсуждение

Я не уверен, что понимаю, почему вы хотите переключиться с таймера 0 на таймер 1, а затем использовать таймер 0 для ШИМ. Почему бы не оставить wiring.c как есть и использовать только timer0 для ШИМ?, @jfpoilpret

Вы написали: «sbi(TIMSK3, TOIE3); // включаем прерывание переполнения таймера 3”. Вы реализовали ISR(TIMER3_OVF_vect) где-нибудь в своей программе?, @Edgar Bonet


3 ответа


0

Я предлагаю оставить код Arduino как есть и написать свои собственные micro() и millis(), используя timer1.

Это должно быть довольно просто.

,

0

Хотя я не уверен, что это полностью сделает все ваши изменения правильными, я вижу потенциальную проблему в одной существующей строке кода, которая может быть плохой из-за использования 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.

,

1

Для заметки: я по ошибке разместил этот вопрос до того, как создал аккаунт, поэтому не могу выбрать лучший ответ; НО, я нашел решение для всех, кто интересуется.

У меня было включено прерывание более чем для одного таймера (забыл отключить прерывания в коде timer0). Однажды я сделал так, чтобы код сработал! Мне удалось переместить функции millis, micros, Delay и DelayMicros в 16-битный таймер 1!

,