Итак, таймер. Вроде вещь эта в AVR простая, но вызывает вопросы. Давайте разбираться вместе.
В AVR все таймеры тактируются от того же источника тактовой частоты, что и ядро. Единственное, что его отделяет от этого источника - это делитель частоты. Важно понимать - делитель частоты ОДИН на все таймеры, но у него несколько выводов. На какой из этих выводов подключить выбранный таймер (любой таймер можно повесить на любой вывод) определяет программист. Давайте эти выводы и запишем:
#define Prescaler_1 Bit(CS10) // Делитель тактов
#define Prescaler_8 Bit(CS11)
#define Prescaler_64 Bit(CS10) | Bit(CS11)
#define Prescaler_256 Bit(CS12)
#define Prescaler_1024 Bit(CS10) | Bit(CS12)
Далее я ввожу ещё одну константу для того, чтобы иметь возможность менять параметры в одном месте. Но этого можно и не делать.
#define Set_Prescaler1 Prescaler_1 // Установка предделителя для таймера 1
Я предполагаю, что МК тактируется от привычных 16МГц, поэтому делением основной тактовой частоты на 1, мы будем тактировать наш таймер частотой ээээ... 16 МГц. Если дать счетчику (а счетчик в таймере - это его главный функциональный элемент) считать до конца, т.е. до 2^16 = 65536, то мы получим, что счетчик будет переполняться примерно 244 раза в секунду. Наверное, это мало, поэтому этот процесс мы будем ускорять. Но прежде мы определим режим работы таймера. Их несколько. Я же выберу режим очистки счетчика таймера при достижении им заданного значения. Этот режим называется CTC:
#define Timer1_Mode_CTC TCCR1B |= (1 << WGM12) | (1 << WGM13);
Чтобы наш таймер заставить считать, его достаточно подключить к делителю:
#define Timer1_Start TCCR1B |= Set_Prescaler1 // Пуск таймера1
Теперь запишем функцию инициализации таймера:
inline void T1_Init(void)
{
TIMSK1 = Bit(TOIE1) | Bit(OCIE1B); // Установка прерываний по переполнению и от компаратора 1B
ICR1 = 10000; // Установка верхней границы счета
}
Здесь мы ограничиваем счет таймера значением 10000, т.е. при достижении этого значения, счетчик таймера автоматически сбросится на ноль. А также разрешаем уход на обработчики прерываний при переполнении счетчика (в нашем случае это 10 000) и при совпадении значений в счетчике и в компараторе 1B (можно использовать и 1A).
Ну и где-то в основном коде запишем:
T1_Init();
Timer1_Mode_CTC
Timer1_Start; // Включаем в работу таймер 1
У 328 камня есть очень большой недостаток - выходы компараторов таймера жестко привязаны к выводам МК. У более новых тинек этот дефект исправлен, ну а мы этот дефект немного подправим программно. Одновременно, т.к. мы разрешили прерывания, то мы обязаны написать обработчики этих прерываний (хотя бы пустые). В противном случае сразу после возникновения прерывания МК будет перезагружен (это эффект от применения компилятора GCC, а не особенность МК!). Обработчики запишем так:
//... Прерывание при переполнении таймера 1
ISR(TIMER1_OVF_vect)
{
}
//... Прерывание от компаратора B таймера 1
ISR(TIMER1_COMPB_vect)
{
}
В обработчиках мы будем заниматься натуральным ногодрыгом. Мой выбор пал на ногу 1 порта A. Но выбрать можно любую другую свободную. Запишем это:
#define PWM_PORT PORTA
#define PWM_DDR DDRA
#define PWM_PIN PINA1 // но можно записать просто "1"
Т.к. сразу после загрузки МК у нас все порты настроены как входы, то где-то в основном коде проинициализируем нашу ножку как выход:
PWM_DDR |= (1<<PWM_PIN);
Ну и теперь осталось малое: в одном из обработчиков прерывания нам необходимо ножку поднять, в другом опустить. Делается это так:
#define ClearBit(reg, bit) reg &= (~(1<<(bit)))
#define SetBit(reg, bit) reg |= (1<<(bit))
#define OFF_PWM_PIN() ClearBit(PWM_PORT, PWM_PIN)
#define ON_PWM_PIN() SetBit(PWM_PORT, PWM_PIN)
Ну и наши обработчики могут выглядеть примерно так:
//... Прерывание при переполнении таймера 1
ISR(TIMER1_OVF_vect)
{
OFF_PWM_PIN()
}
//... Прерывание от компаратора B таймера 1
ISR(TIMER1_COMPB_vect)
{
ON_PWM_PIN()
}
Скважность ШИМ сигнала меняется путем записи любого значения от 0 и до 10000 в регистр компаратора 1B:
OCR1B = 5000;
Ну вот и всё.