Содержание
Урок 1. Первый проект
Урок 2. Управление кнопками
Урок 3. Подключение LCD
Урок 4. Использование ШИМ
Урок 5. Таймеры
Урок 6.1. Статическая индикация
Урок 6.2. Динамическая индикация
Урок 7.1. Генерация звука
Урок 7.2. Генерация звука. Продолжение
Урок 8.1. Передача данных через UART
Урок 8.2. Передача данных через UART. Продолжение»
Урок 9. Передача данных через SPI
Урок 10. Изучение АЦП. Простой вольтметр
Урок 11. Получение синуса при помощи ШИМ
Урок 12. Измерение температуры
Урок 13. Внешние прерывания.
Урок 14. Использование отладчика
Урок 15.1. Управление инкрементальным энкодером
Урок 15.2. Управление громкостью, при помощи энкодера
Урок 16. Управление RGB светодиодом
Урок 17. Использование ИК
Урок 18.1. Знакомство с графическим дисплеем
Урок 18.2 Вывод изображения на графический дисплей
Урок 18.3 Вывод русскоязычного текста
Урок 19. Формирование сигнала, при помощи ЦАП (R2R)
Урок 20. Опрос матричной клавиатуры
Урок 21. Сторожевой таймер
Урок 22.1 Воспроизведение wav. Введение.
Урок 22.2 Воспроизведение wav. Продолжение.
Урок 23.1 Работа с внешней памятью
Урок 23.2 Работа с файловой системой Fat

Прежде чем приступить к изучению таймера определимся с базовым понятием «частота». Простым языком, это количество повторений, в секунду. Это значит, что если вы за секунду хлопнете в ладошки 2 раза, то частота хлопков будет равна 2Гц. Если за 3 раза, значит 3Гц.

Каждый микроконтроллер работает на определенной частоте. Большинство инструкций выполняется за один такт, поэтому чем выше частота, тем быстрее работает микроконтроллер. Если нет источника тактирования, соответственно ничего работать не будет. На случай отсутствия внешнего источника тактирования, в большинстве микроконтроллеров имеется свой внутренний генератор. Обычно на него «с завода» настроены.

Частота внутреннего источника может изменяться («плавать») из за температуры и т.п., поэтому считается непригодным для серьезных проектов, а у нас ведь именно такие 🙂 Поэтому применяется стабильный источник внешней частоты — кварцевый резонатор (кварц). Один из вариантов исполнения кварцевого резонатора:

Теперь, кое что о таймере. Таймер работает на той же частоте, что и микроконтроллер. Иногда это может быть слишком быстро, поэтому используют предделитель который уменьшает количество тиков в 8/64/256/1024… раз. Включается это все программно.

Допустим, мы выбрали предделитель 1024, частота микроконтроллера 8 МГц, значит после предделителя частота таймера станет:
8 000 000 / 1024 = 7813 Гц — это частота, на которой работает наш таймер. По простому говоря, за одну секунду таймер тикнет 7813 раз.

К количеству тиков можно привязать выполнение кода. Эта фича есть не для всех таймеров, читайте документацию на свой камень. Допустим, нам нужно, чтобы раз в 0,5 секунды выполнялся наш код. За одну секунду 7813 тиков, за пол секунды в 2 раза меньше — 3906. Это значение вносится в регистр сравнения, и с каждым тиком проверяется достаточно ли оттикало или нет, как в будильнике, только очень быстро.

Но вот у нас совпали эти 2 значения и что дальше? Для этого существует такая полезная штука как прерывание по совпадению. Это значит, что при совпадении таймера и регистра сравнения, ваша текущая программа остановится. После этого выполнится участок кода, который абсолютно не связан с основной программой. Внутри этого участка вы можете писать что угодно и не беспокоиться о том, что он как то повлияет на программу, выполнится он только когда значение таймера совпадет с регистром сравнения.

После того как код внутри прерывания выполнится, программа продолжит работу с того места, где была остановлена. Таким образом, можно периодически сканировать кнопки, считать длительность нажатия кнопки, отмерять точные временные промежутки. Любимый вопрос начинающих, как мне делать мигать светодиодом и делать еще что то. Так вот, в этом вам помогут таймеры и прерывания.

Вот теперь мы готовы написать нашу программу. Поэтому создаем проект с помощью мастера проектов. Сразу прицепим LCD, мы же уже это умеем).

Переходим на вкладку Timers и тут остановимся поподробнее:

Выбираем частоту 7813 и устанавливаем галочку напротив пункта Interrupt on: Compare A Match. Таким образом мы указали, что при совпадении значения выполнять прерывание (то о чем было написано выше). Прерывание будем выполнять 1 раз в секунду, т.е. нам нужно тикнуть 7813 раз, поэтому переводим число 7813 в шестнадцатеричную систему и получим 1e85. Именно его и записываем в регистр сравнения Comp A. Регистр сравнения Comp A 16 битный, поэтому число больше 2^16=65536 мы записать не можем.

Генерим, сохраняем, вычищаем наш код. Появится новый непонятный кусок кода

// Timer 1 output compare A interrupt service routine
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{

}

Это то самое прерывание. Именно внутри этих скобок мы можем писать тот код, который мы хотели бы выполнять через определенные промежутки времени. У нас это одна секунда. Итак логично создать переменную, которую мы будем увеличивать 1 раз в секунду, т.е. 1 раз за прерывание. Поэтому проинициализируем переменную int s =0; а в прерывании будем ее увеличивать от 0 до 59. Значение переменной выведем на жк дисплей. Никаких хитростей, все очень просто.
Получившийся код.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <mega8.h>
 
#asm
   .equ __lcd_port=0x18 ;PORTB
#endasm
#include <lcd.h>
 
int s = 0; // переменная для хранения секунд
 
// Обработка прерывания по совпадению
interrupt [TIM1_COMPA] void timer1_compa_isr(void)
{
   s++; // увеличиваем переменную каждую секунду
   if(s>59) // обнуляем секунды после 59
   {
      s=0;
   }
  TCNT1=0; //обнуляем таймер
}
 
void main(void)
{
 
TCCR1A=0x00; //настройка таймера
TCCR1B=0x05;
TCNT1=0x00; //здесь увеличиваются тики
OCR1A=0x1E85; //записываем число в регистр сравнения
 
TIMSK=0x10; //запускаем таймер
 
lcd_init(8);
 
#asm("sei")
 
while (1)
      {
        lcd_gotoxy(0,0);  //вывод в 0 координате X и Y
        lcd_putchar(s/10+0x30); //вывод десятков секунд
        lcd_putchar(s%10+0x30); //вывод секунд
      };
}

Прикрутив еще 2 переменные можно получить часы на микроконтроллере).
Файл прошивки и протеуса

212 комментариев: Урок 5. Использование таймера в AVR микроконтроллерах

  • Можете привести пример кода где таймер запускается по прерыванию с ноги МК, отсчитывает определенное время и выполняет код, потом все с начала.

  • Кусок абстрактный, но логика должна быть понятна

    interrupt [EXT_INT0] void ext_int0_isr(void)
    {
     //включаем таймер
     TCCR1A=0x00;
     TCCR1B=0x05;       
    }
    interrupt [TIM1_COMPA] void timer1_compa_isr(void)
    {
    //делаем что то
    ...
    //выключаем таймер
     TCCR1A=0x00;
     TCCR1B=0x00; 
     TCNT1H=0x00;
     TCNT1L=0x00;   
    }
    
  • Не могли бы вы пояснить данный код.
    lcd_putchar(s/10+0x30);
    lcd_putchar(s%10+0x30);

  • s/10 — вывод десятков секунд
    s%10 — вывод единиц секунд
    +0х30 это смещение для кодировки ASCII

  • С прерыванием разобрался но вот на деле не получается, я хочу сделать с помощью прерывания по совпадению чтоб после включения мк через 5 секунд загорелся светодиод. Сделал как в примере с помощью переменной s , только в основном цикле написал PORTB.0=1. Получается при включении мк , светодиод зпгорается сразу а не через 5 секунд, почему так?

  • Значит с прерываниями не разобрались. Они выполняются не зависимо от основного цикла, но это не значит что код в основном цикле не будет выполняться. Если вы включили в основном цикле светодиод, то он и включится. Варианты: 1. включать его внутри прерывания, 2. выставлять флаг в прерывании, а в основном цикле по нему включать.

  • int s = 0; // переменная для хранения секунд
     
    // Обработка прерываний
    interrupt [TIM1_COMPA] void timer1_compa_isr(void)
    {
    s++; // увеличиваем переменную каждую секунду
    if(s>5)
    {
    PORTB.0=1;
    }
    Вот так сделал , все равно не работает

  • Работает , только не через 5 секунд включается а через 43, получается какие то секунды длинные, делитель на 8 отключен в мк, частота 8 мгц. Использую 2313 первый таймер, работаю в CVAVR ,предделитель на 7813, режим выставлял и normal top=0xffff и ctc top=ocr1a и все равно одинаково длинные секунды, не пойму что не так

  • надо остальной код смотреть

  • int i;
    Код в прерывании
    {
    i++;
    if(i==5)
    {PORTB.0=1}
    }
    основной цикл пустой

  • Работает, только админ, у вас ошибка там где в мастере настраивали режим таймера нужно ставить не normal top=0xffff(прерывание по переполнению) а ctc top=ocr1a (прерывание по совпадению)тогда работает как надо, а если так как показано , то секунды становятся в 4 раза больше почему то, исправте рисунок если можно, а то по нем если выставлять то не получается как надо

  • там нет ошибки 🙂

  • Хорошо, тогда почему секунды длятся так долго?

  • Работает, но как то не стабильно секунда в секунду, с каждым разом секунда длится по разному, иногда попадает в такт иногда дольше, это если выставить как на рисунке и в листинге обнулять счетчик нужно, а если не обнулять то светодиод загорается раз в 8 секунд. А если поставить режим прерывание по совпадению то там счетчик не нужно обнулять , и работает стабильно, по крайней мере разницу видно. Почему так получается? Или это такая специфика использования режимов?

  • Специфика режимов таймера.

  • как делать аналогичные прерывания, используя timer2?

  • Выбираете частоту таймера, считаете требуемое время для генерации прерывания. Пишите это число в регистр сравнения. Все настройки можно сгенерить мастером CAVR

  • Привет админ avr-start!
    Я часто посешаю на ваш сайт и учусь маленько здесь, и Вы мне очень помогаете.
    Я сделал сборку звонка для школы.
    У меня есть не большой опыт на языке программирования на AVR Studio. Дайте направления для создания программу который показывает часы, минуты, секунд и дни недели в мониторе 16×2 LCD.

  • Если таймер восьми битный (AtTiny26L) как правильно можно организовать большие задержки. Т.к. при таком методе прерывание можно вызвать максимум 30 раз в секунду (частоту выставить 7813 гц а в регистр сравнения впихнуть FF.

  • можете сделать переменную которую увеличиваете 1 раз за прерывание, и например когда переменная достигнет 100 — выполнить код

  • Не хотел использовать глобальную переменную но придется. В прерывании менять значение переменной. Там же проводить проверку. 2кб на борту особо не раскрутишься.

  • Здравствуйте. А можно пример кода когда таймер запускается по импульсу на одном пину и останавливается по импульсу на втором пинет микроконтроллера а колличество импульсов записывает в переменную.
    Спасибо.

  • посмотрите второе сообщение

  • Большое спасибо за урок. Раньше не программировал, и возник вопрос: требуется счетчик до 15 (4бита), нужно вывести осчеты на 7-4 ножки порта D микросхемы, или любые другие.
    Спасибо.

  • самое простое для восприятия — оператор switch
    switch(time)
    {
    case 0:
    PORTD=0b00100000;
    break;
    case 1;
    PORTD=0b00110000;
    break;
    ….
    }

  • Спасибо, switch действительно прост для восприятия, но что делать в случае если счетчик до 256 , а используются, к примеру, 2 ножки порта D, 1 — F и 5 — C. Описывать отдельно каждое состояние бессмысленно. Хочется найти более «красивое» решение..
    Прошу прощения за глупые вопросы, просто любопытно)

  • if(time==0x0F){PORTD=….}
    if(time==0x0C){PORTD=….}

  • делаю часики, секунды и минуты все нормально считает, а часы не прибавляет, в чем моя ошибка?
    interrupt [TIM1_COMPA] void timer1_compa_isr(void)
    {
    s++; // увеличиваем переменную каждую секунду
    if(s>3) // обнуляем секунды после 3
    {
    if(s==3); //если s=3,то м+1
    {m++; // прибавляем минуты
    };
    s=0; //обнуляем секунды

    if(m>3) // обнуляем минуты после 3
    {
    m=0; // обнуляем минуты
    };

    if(m==3); //если m=3,то ч+1
    {i++; // прибавляем часы
    };
    m=0; // обнуляем минуты

    if(i>3) // если часы больше 3
    {
    i=0; // обнуляем часы
    };

    }

  • минуты постоянно обнуляются

  • Ув.админ сделайте пожалуйста кусок кода когда можно с помощью кнопки выставить секунды, или подскажите правильное направление, за раннее говорю спасибо!

  • if(PINC.0 == 0)
    {
    sek++;
    }

  • Здравствуйте! Почти то что мне нужно))), Точнее, я ищу пример кода, когда в режиме Normal таймер Т1 вызывает два различных обработчика прерывания — TIM1_COMPA и TIM1_COMPВ. Не можете помочь?

  • галочку в генераторе поставить

  • Здравствуйте! Планирую изготовить эмулятор датчика (ДПКВ) но не знаю как подскажите пожалуйста как правильно это сделать!!! Датчик представляет из себя зубчатое колесо с 60-ю зубьями из которых два специально срезаны. На осцилограмме должно в итоге получиться меандр из 58 выступов и 2 пропусков и пять все повторяется. Но есть еще одно условие кол-во зубьев и кол-в спиленных зубьев должно задаваться константами т.к бывают разные шкивы.
    пока у меня только такая идея таймер считает от 0 до 120 а в завсимости от того какой счет подаем на ножку МК напряжение или снимаем.
    if(s==1)PORTC.0=1;
    if(s==2)PORTC.0=0;
    if(s==3)PORTC.0=1;
    if(s==4)PORTC.0=0;
    if(s==5)PORTC.0=1;
    if(s==6)PORTC.0=0;
    ————————-
    ————————-
    ————————-
    if(s==117)PORTC.0=0;
    if(s==118)PORTC.0=0;
    if(s==119)PORTC.0=0;
    if(s==120)PORTC.0=0;
    Спасибо!!!

  • Первое что в голову пришло

    #define ZUB 116
    #define NET_ZUBOV 4
     
    ...
    ...
    if(s<ZUB)
    PORTC.0=!PORTC.0;
    else 
    {
          if((s-ZUB) < NET_ZUBOV)
            PORTC.0=0;
          else
              s=0;        
    }
  • подскажите а обязательно значение в регистр OCR1A записывать в 16ричном формате? и обязательно ли записывать значение сначала в старший а потом младший, т.е. вот так:

    OCR1AH=0x03;//записываем в регистр OCR1A 1000
    OCR1AL=0xE8;
    или можно просто

    OCR1A=0x03E8;

  • можно, однако не всегда это прокатывает

  • Добрый день!
    Начал изучать мк потихоньку, набросал программку которая по прерыванию раз в минуту меряет температуру через ds18b20 и в зависимости от полученного результата зажигает светодиод нужного цвета.
    Вроде все правильно, но при прогоне в протеусе почему то всегда горит светодиод подключенный к ноге PB0
    Помогите пожалуйста разобраться где ошибка!

    #include
    #include

    // 1 Wire Bus functions
    #asm
    .equ __w1_port=0x18 ;PORTB
    .equ __w1_bit=3
    #endasm
    #include

    // DS1820 Temperature Sensor functions
    #include
    int s=0;
    float temp;
    // Timer1 output compare A interrupt service routine
    interrupt [TIM1_COMPA] void timer1_compa_isr(void)
    {
    // Place your code here
    s++;
    if (s>59)
    {
    w1_init();
    delay_ms(500);
    ds18b20_init(0,-20,50,DS18B20_12BIT_RES);
    delay_ms(1000);
    temp=ds18b20_temperature(0);
    s=0;
    }
    TCNT1=0;
    }

    // Declare your global variables here

    void main(void)
    {
    // Declare your local variables here

    // Crystal Oscillator division factor: 1
    #pragma optsize-
    CLKPR=0x80;
    CLKPR=0x00;
    #ifdef _OPTIMIZE_SIZE_
    #pragma optsize+
    #endif

    // Input/Output Ports initialization
    // Port B initialization
    // Func5=In Func4=In Func3=In Func2=Out Func1=Out Func0=Out
    // State5=T State4=T State3=T State2=0 State1=0 State0=0
    PORTB=0x00;
    DDRB=0x07;

    // Timer/Counter 0 initialization
    // Clock source: System Clock
    // Clock value: Timer 0 Stopped
    // Mode: Normal top=FFh
    // OC0A output: Disconnected
    // OC0B output: Disconnected
    TCCR0A=0x00;
    TCCR0B=0x00;
    TCNT0=0x00;
    OCR0A=0x00;
    OCR0B=0x00;

    // Timer/Counter 1 initialization
    // Clock source: System Clock
    // Clock value: 7,813 kHz
    // Mode: Normal top=FFh
    // OC1A output: Disconnected
    // OC1B output: Disconnected
    // Timer1 Overflow Interrupt: Off
    // Compare A Match Interrupt: On
    // Compare B Match Interrupt: Off
    PLLCSR=0x00;

    TCCR1=0x0B;
    GTCCR=0x00;
    TCNT1=0x00;
    OCR1A=0x1E85;
    OCR1B=0x00;
    OCR1C=0x00;

    // External Interrupt(s) initialization
    // INT0: Off
    // Interrupt on any change on pins PCINT0-5: Off
    GIMSK=0x00;
    MCUCR=0x00;

    // Timer(s)/Counter(s) Interrupt(s) initialization
    TIMSK=0x40;

    // Universal Serial Interface initialization
    // Mode: Disabled
    // Clock source: Register & Counter=no clk.
    // USI Counter Overflow Interrupt: Off
    USICR=0x00;

    // Analog Comparator initialization
    // Analog Comparator: Off
    ACSR=0x80;
    ADCSRB=0x00;

    // 1 Wire Bus initialization
    w1_init();

    // Global enable interrupts
    #asm(«sei»)

    while (1)
    {
    // Place your code here
    if (temp > 23)
    {
    PORTB.2=0x01;
    }
    else if (temp 18)
    {
    PORTB.1=0x01;
    }
    else if (temp <18)
    {
    PORTB.0=0x01;
    }
    };
    }

  • вот эта строчка стремная else if (temp 18)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Последние комментарии
  • Загрузка...
Счетчик
Яндекс.Метрика