What timer does millis arduino function use. Overflow timer interrupt

/* ISR_Blink Same fruit, just from a different angle Flashing the LED using the interrupt mechanism (timer/counter 2 overflow) */ volatile long mks100; volatile long ms10; volatile int cntr; long tmillis,tms10=0; char flip; void setup() ( mks100 = 0; // counter of hundreds of microseconds, counter overflows after about 5 days ms10 = 0; // counter of tens of milliseconds, counter overflows after about 16 months cntr = 0; flip = 0; // blink standard LED . // On most Arduino boards, it is connected to pin 13: pinMode(13, OUTPUT); Serial.begin(9600); // Turn on the timer / counter mode we need - normal TCCR2A = 0; // normal mode (according to default 1 - phase corrected PWM?) // Set the timer/counter prescaler to 16 - // this will allow the timer to "tick" every microsecond // (assuming the microcontroller's heart is beating at // 16.000.000 beats per second) TCCR2B = 2; // 010 - fclk/8 (default 100 - fclk/64) //TCCR2B = 7; // 111 - fclk/1024 (default 100 - fclk/64) TCNT2=59;//55; TIMSK2 |= (1<< TOIE2); // разрешаем прерывание таймера/счетчика 2 по переполнению } ISR(TIMER2_OVF_vect) { // прежде всего взводим счетчик TCNT2=59;//55; // прошли очередные 100 мксек - увеличиваем счетчик сотен микросекунд mks100++; // if(mks100%100==0) ms10++; cntr++; // прошли очередные 10 мсек? - увеличиваем счетчик десятков миллисекунд if(cntr>99) ( ms10++; cntr = 0; ) ) void loop() ( if (ms10>tms10) ( tmillis = millis(); tms10 = ms10; if (tms10%100==0) ( if(flip) digitalWrite(13 , HIGH); // set the LED on else digitalWrite(13, LOW); // set the LED off flip = !flip; ) if (tms10%1000==0) ( // execute every 10 seconds Serial.print( tmillis,DEC); Serial.print(" milliseconds, "); Serial.println(tms10,DEC); ) ) )

  • Categorized in
  • Tagged with

One comment

When I wanted to develop with Arduino, I ran into several problems:
  • Selecting a model from the list of available
  • Trying to understand what I need besides the platform itself
  • Installing and configuring the development environment
  • Finding and parsing test cases
  • "Disassembly" with the screen
  • "Disassembly" with the processor

To solve these problems, I looked and read quite a lot of different sources, and in this article I will try to make an overview of the solutions I found and methods for finding them.

Platform selection

Before starting programming for a piece of iron, you need to buy it at the beginning. And then I ran into the first problem: it turned out that there are quite a lot of different *duins. There is also a wide range of Arduino and about the same wide Freeduino and other analogues. As it turned out, there is no big difference what exactly to take. That is, some of these devices are a little faster, others a little slower, some are cheaper, others are more expensive, but the basic principles of operation are practically the same. Differences appear practically only when working with processor registers, and then I will further explain how to avoid problems as much as possible.

I chose the Arduino Leonardo platform as the most affordable and available at that time in the Internet store, in which I ordered everything. It differs from the rest of the line in that it has only one controller installed on board, which deals with both working with the USB port and performing the very tasks that we will hang on our device. This has its pros and cons, but you won’t be able to run into them during the initial study, so let’s forget about them for now. It turned out that it connects to the computer via micro-USB, and not USB-B, like most others seem to be. This surprised me a little, but also made me happy, because, as the owner of a modern Android device, I don’t leave the house at all without this cable.
Yes, almost any *duino-compatible piece of iron is powered in several ways, including from the same cable through which it is programmed. Also, almost all boards have one LED directly on the controller board, which allows you to start working with the device immediately after purchase, even without having anything in your hands except a compatible cable.

Spectrum of tasks

I think that before undertaking as such writing something for a piece of iron, it is interesting to understand what can be implemented on it. Almost anything can be done with Arduino. Automation systems, ideas for a smart home, controllers to control something useful, the “brains” of robots… There are just a lot of options. And a fairly wide range of expansion modules that are extremely easy to connect to the controller board helps a lot in this direction. Their list is quite long and promising, and they are searched on the Internet for the word shield. Of all these devices, I found the most useful LCD screen with a basic set of buttons, without which, in my humble opinion, it is completely uninteresting to engage in any training projects. The screen was taken from here, there is also a description of it, as well as links to the official website of the manufacturer from the above page.

Formulation of the problem

When I get a new tool in my hands, I somehow got used to immediately set myself some moderately complex and absolutely unnecessary task, solve it bravely, and then put the source code aside and only then take on something really complex and useful. Now I had at hand a screen very similar to a component of a mine from some Hollywood movie with all the necessary buttons that I had to learn how to work with, and I also really wanted to learn how to work with interrupts (otherwise what is the point of using a controller?) so the first the same thing that came to mind, it turned out to write a clock. And since the screen size allowed, then also with the date.

The first steps

So I finally got all the purchased components and I assembled them. The screen connector connected to the controller board as a native one, the board was connected to the computer ... And then this article helped me a lot. I will not repeat the same.

Hidden text

The only thing I can say is that, remembering my youth (or rather, the first “project” assembled while studying radio electronics at the Pioneer Palace - a multivibrator with two LEDs), I found 2 LEDs and corrected the example given in the article and started flashing them :).

"Second Steps"

The next logical question for me was “how to work with the LCD screen?”. The official page of the device kindly provided me with links to the archive, which contained 2 libraries with great examples. She just didn't say what to do about it. It turned out that the contents just need to be unpacked into the libraries folder of the development environment.

After that, you can open the GuessTheNumber.pde example and upload it to the board in the same way as the blinking LED example. However, personally, after the firmware, the screen remained evenly luminous and without a single letter. After a short search for the problem, it turned out that it was necessary to simply twist the only potentiometer available on the screen board with a screwdriver to set the normal contrast value.

The command set used in the example is, in principle, sufficient for simple work with the screen, but if you want something more, you can open the source text of the LCDKeypad and LiquidCrystal libraries and see what else is there.

Program architecture

The main task of a watch is to count time. And they must do it for sure. Naturally, without using the interrupt mechanism, no one can guarantee that the time is calculated with sufficient accuracy. Therefore, the calculation of time should definitely be left to them. Everything else can be moved into the body of the main program. And we have quite a lot of this “rest” - all the work with the interface. It would be possible to do otherwise, create an event stack, which is also created by the interrupt handling mechanism, and processed inside the main application, this would allow, for example, updating the screen no more than once every half a second (or by pressing a button), but I calculated this superfluous for such a simple task, because apart from redrawing the screen, the processor still has nothing to do. Therefore, all free time the program rereads the state of the buttons and redraws the screen.
Problems associated with this approach
Periodic screen changes
I really wanted to make flashing colons between hours, minutes and seconds, so that, like in a classic watch, they would burn for half a second, but not for half a second. But since the screen is redrawn all the time, it was necessary to somehow determine in which half of a second they should be drawn, and in which not. The simplest was to do 120 seconds per minute and draw colons every odd second.
flickering
With constant redrawing of the screen, flickering becomes noticeable. To avoid this, it makes sense not to clear the screen, but to draw new text on top of the old one. If the text itself does not change, then there will be no flickering on the screen. Then the time redrawing function will look like this:
LCD Keypad LCD; void showTime()( lcd. home(); if (hour<10) lcd.print("0"); // Случай разной длины приходится обрабатывать руками lcd.print(hour,DEC); // английские буквы и цифры ОНО пишет само, русские буквы нужно определять программисту if (second %2) lcd.print(" "); else lcd.print(":"); // вот они где используются, мои 120 секунд в минуте if (minute<10) lcd.print("0"); lcd.print(minute,DEC); if (second %2) lcd.print(" "); else lcd.print(":"); if (second<20) lcd.print("0"); lcd.print(second / 2,DEC); lcd.print(" "); lcd.setCursor(0,1); // переходим в координаты x=0, y=1 то есть в начало второй строки lcd.print(" "); lcd.print(day,DEC); lcd.print(months); // месяцы мне было приятнее нумеровать от 1 до 12, а массив с названиями от 0 до 11 lcd.print(year,DEC); }
Working with buttons
The situation is similar with buttons. The pressed button is counted as pressed each time the program is run, so any number of times can be processed in one click. You have to force the program to wait for "push-ups" separately. Let's start the main program like this:
intlb=0; // the variable stores the old value of the button void loop()( // main program int cb,rb; // define 2 variables, for the button actually pressed and for the one that the program will consider pressed cb=rb=lcd.button(); // at the beginning, we can assume that this is the same button if (rb!=KEYPAD_NONE) showval=1; // the variable indicates that while the button is pressed, what is being configured should not blink if (cb!=lb) lb= cb; // if the state of the button has changed, remember the new one, else cb=KEYPAD_NONE; // otherwise we tell the program that all buttons have been released a long time ago.

Working with a timer

Actually, so that all work with the timer consists of two important components:
  • Initializing the interrupt mechanism from the timer in a convenient mode for us
  • Actually, interrupt handling
Timer initialization
In order to start receiving the interrupts we need, we need to configure the processor so that it starts generating them. To do this, we need to set the registers we need to the desired values. Which registers and exactly which values ​​​​to be set, you need to look in ... the datasheet for the processor :(. Honestly, I really hoped that this information could be found in the documentation for the Arduino itself, but no, it would be too simple. Moreover , for different processors of the series, the bit numbers may differ.And I personally came across the fact that an attempt to set the bits in accordance with the datasheet on the neighboring processor led to disastrous results ... But nevertheless, everything is not as sad as it might seem, because for there are also names for these bits, they are more or less common for different processors... Therefore, we will not use digital values, only names.

To begin with, remember that there are several timers in AVR microcontrollers. Zero is used to calculate delay() values ​​and things like that, so we won't use it. Accordingly, we use the first one. Therefore, further in the designation of the registers, one will often slip, to set up, say, the second timer, you need to put a deuce there.

All timer initialization must occur in the setup() procedure. It consists of placing values ​​in 4 registers, TCCR1A, TCCR1B, TIMSK1, OCR1A. The first 2 of them are called "timer-counter 1 control registers A and B". The third is "timer/counter 1 interrupt mask register" and the last is "counter 1 comparison register A".

It is customary to use the following commands for setting bits (it is clear that there are many options, but these are the most commonly used):
BITE |= (1<< POSITION)
that is, we push a "1" on the POSITION bit from right to left and draw a logical "or" between the target and received bytes. When the controller is turned on, the values ​​of all these registers contain 0, so we simply forget about zeros. So after executing the following code

A=0; A |= (1<< 3)

The value of A will become 8.

There are a lot of options for setting the timer, but we need to achieve the following from the timer:

  • In order for the timer to switch to the CTC mode (that is, to the counting mode with reset after a match, "Clear Timer on Compare match"), judging by the datasheet, this is achieved by setting bits WGM12: 0 = 2, which in itself means setting bits from the second to zero to the value "2", that is, "010", command TCCR1B |= (1<< WGM12) ;
  • Since 16 MHz (namely, such a frequency for a quartz resonator on my board) is a lot, choose the maximum possible divider, 1024 (that is, only every 1024th cycle will reach our counter), CS12: 0 = 5
  • Make it so that the interrupt comes when it matches register A, for this counter TIMSK1 |= (1<< OCIE1A)
  • Specify when it reaches which value to call interrupt processing, this value is placed in the same register A of counter 1 (its entire name is OCR1A), the interrupt, by coincidence with which we included the previous paragraph.

How to calculate how long we need to carry out calculations? - It's easy, if the clock frequency of the quartz resonator is 16 MHz, then when the counter reaches the value of 16000, a second would pass if the division ratio was 1. Since it is 1024, we get 16000000/1024=15625 per second. And everything would be fine, but we need to get values ​​​​every half a second, and 15625 is not divisible by 2. So we made a mistake before and we have to take a smaller division factor. And the next one down we have 256, which gives 62500 ticks per second, or 31250 in half a second. We have a 16-bit counter, so it can count up to 65536. In other words, it is enough for us for half a second and for a second. We climb into the datasheet, then into the source and fix it to CS12:0=4 , and after that OCR1A = 31249; (as I understand it, one cycle goes either to reset, or somewhere else, so there are tips to reset another one from the received number).

Interrupt handling
The syntax of the interrupt handling function has changed somewhat, now it looks like in the example below. So don't be surprised if you see a slightly different description of the function name somewhere.

Actually, now it consists of the reserved word ISR and an indication of the specific interrupt that this function handles in brackets. And inside this function, as you can see, there is nothing fantastic. Even the mandatory RETI, as you can see, is automatically inserted by the compiler for us.

ISR(TIMER1_COMPA_vect) ( digitalWrite(LEDPIN, !digitalRead(LEDPIN)); // LEDPIN=13. This line blinks the LED on the board. Convenient and cool:) second++; if ((second %2) && lastshowval) ( // this and the next 7 lines are just for lastshowval = 0; // to be able to achieve this funny effect, like on a hardware clock, showval = 0; // when in in setup mode, let's say minutes, the value of the adjustable parameter blinks ) if (!(second %2) && !lastshowval)( // only when the buttons are released, while the buttons are pressed, it just lights up. lastshowval = 1; showval = 1; ) if ( second>=120) ( // again my 120 seconds in a minute. Well, who cares now? second-=120; minute++; if (minute>=60)( minute-=60; hour++; if (hour>=24) ( hour-=24; day++; if (daylarge(day,month,year) // returns true if the day value // is greater than the maximum possible for this month of this year.) ( day=1; month++; if (month>12) ( month = 1; year++; ) ) ) ) ) )

I hope this article will be useful to someone, because there are quite a few detailed instructions on the topic of working with timer interrupts in Russian.

Lesson 10

Timers-counters. Interrupts

Today we will find out what is timers-counters in microcontrollers and what they are for, as well as what is interrupts and why they are needed.

Timers-counters- these are devices or modules in the microcontroller, which, as the name implies, constantly consider something. They count either up to a certain value, or up to such a value as they are bits. They count constantly at the same speed, with the speed of the microcontroller clock frequency, corrected for frequency dividers, which we will configure in certain registers.

And these timer-counters are constantly counting if we initialize them.

Timers in MK Atmega8 three.

Two of them are eight-bit timers, that is, those that can only count up to 255 as much as possible. This value will not be enough for us. Even if we apply the maximum frequency divider, then we won’t count a second, we won’t be able to count even half a second. And our task is exactly this, to count up to 1 second in order to control the increment of the LED indicator count. Of course, you can also apply an increase in the variable to a certain value, but I would like a completely hardware account.

But there is another timer - this is a full-fledged 16 bit timer. He not only 16 bit, but there are still certain charms in it that other timers do not have. We'll look at these options later.

It is this 16-bit timer that we will study and use today. Also, having become acquainted with this timer, it will not cost you anything to study the work of the other two on your own, since they are much simpler. Nevertheless, we will also consider 8-bit timers in the future, since one timer will not be enough for us to achieve more complex tasks.

Now briefly about interrupts.

Interrupts (Interrupts) are mechanisms that break the code depending on certain conditions or a certain situation, which will dictate some of the devices, modules and buses that are in the microcontroller.

There are 19 types of interrupts in our Atmega8 controller. Here they are all in the table in the technical documentation for the controller

What type of conditions can be? In our case, for example, the timer counted up to a certain value, or, for example, a byte and other conditions came to some bus.

At the moment, we will process the interrupt, which is located in the table placed 7 positions higher - TIMER1 COMPA, called at address 0x006.

Now let's look at our 16-bit timer, or TIMER1.

Here is its structure

We see a register there TCNTn, in which the number is constantly changing, that is, it is constantly increasing. In fact, this is the counter. That is, this register stores the number to which the timer has counted.

And in registers OCRNA and OCRnB(the letters n are the number of the timer, in our case it will be 1) are the registers in which we enter the number with which the chilo will be compared in the TCNTn register.

For example, we entered some number into the OCRnA register, and as soon as this number matches the value in the count register, an interrupt will occur and we can process it. Timers with interrupts are very similar to the usual delay in the code, only when we are in a delay, then we cannot execute any code at that time (well, again, figuratively "we", actually ALU). And when the timer counts, then the entire code of our program is being quietly executed at that time. So we win enormously by not letting huge controller resources idle for a second or even half a second. At this time, we can process button presses, which we can also process in a timer and more.

There is also a TCCR register. This register is the control register. There, certain bits are configured that are responsible for the configuration of the timer.

The timer also has several modes, which we will also get to know a little later.

It consists of two halves, since we have an 8-bit controller and it cannot have 16-bit registers. Therefore, in one half of the register (and physically in one register), the upper part of the register is stored, and in the other, the lower part. You can also call it a register pair consisting of two separate registers TCCR1A and TCCR1B. The number 1 means that the register belongs to timer 1.

This TCCR register is responsible for setting the divider so that the timer does not count so quickly, it is also responsible (or rather, its certain bits) for setting a certain mode.

The WGM bits are responsible for setting the mode.

We see here a lot of varieties of regimes.

Normal- this is the normal mode, the timer counts to the end.

PWM- this is PWM only different varieties, that is, the timer can play a role pulse width modulator. We will get acquainted with this technology in later lessons.

CTC- this is a reset by coincidence, just what we will need. This is where the TCNT and OCR registers are compared. There are two such modes, we need the first one, the second one works with a different register.

We will not study all types of modes in this lesson. When we need these modes, then we'll figure it out.

Well, let's not torment ourselves with documentation and finally try to put something into some registers.

The code, as always, was created from a previous project. For the proteus, the code was also copied and renamed from the last lesson, and the path to the new firmware was also indicated in the controller properties. We will name the projects Test07.

Let's try to compile the code as always and run it in Proteus. If everything works fine, then we start adding new code.

Let's add one more function, since we learned how to add functions in the last lesson. The function code will be placed after the segchar function and before the main function. After, due to the fact that we will call the segchar function inside our new function.

Moreover, we will create not one function, but two. In one function, we will place all the initialization code for our timer, and the other function will be the timer interrupt handler, and such functions are specific and you do not need to call them. When the need arises, they will be called themselves, depending on certain conditions that were agreed above.

Therefore, we will call the first function timer_ini

//———————————————

voidtimer_ini( void)

{

}

//———————————————

Also, give our functions, as well as some complete blocks with the declaration of global variables, with function prototypes, we will separate from each other with such dashes that the compiler will not process due to the presence of two slashes in front and will take them for comments. Through these outlines, we will see where one function ends and another begins.

This function, as we can see, has no arguments - no input, no return. Let's immediately call this function in the main () function

unsignedcharbutcount=0,butstate=0;

timer_ini();

Now we will begin to slowly fill this function with code.

Let's start with a timer control register, such as TCCR1B. Using our favorite "OR" operation, we will put one in a certain bit of the register

voidtimer_ini( void)

TCCR1B|= (1<< WGM12);

From the comment, we see that we are working with mode bits, and we will set only the WGM12 bit from them, the rest will be left zero. Based on this, we configured the following mode:

The timer also has such a register - TIMSK. This register is responsible for interrupt masks - interrupt mask. This register is available for all timers, not only for the first one, it is common. In this register, we will set the bit OCIE1A, which will enable the type of interrupt we need TIMER1 COMPA

TCCR1B|= (1<< WGM12); // set CTC mode (reset by coincidence)

TIMSK|= (1<< OCIE1A);

Now let's play with the comparison registers themselves OCR1A(H and L). To do this, you have to do some math. Register OCR1AH stores the high part of the number for comparison, and the register OCR1AL- younger.

But before counting, let's write the code for now with any values ​​​​of this register and then fix it, since then we will initialize the divisor and it will also participate in the calculation of the required counting time. Without a divider, the timer will count too fast.

TIMSK|= (1<< OCIE1A); //set the interrupt enable bit of the 1st counter by coincidence with OCR1A(H and L)

OCR1AH= 0b10000000;

OCR1AL= 0b00000000;

TCCR1B|= ( ); //set the divisor.

We do not set any divisor yet, since we have not yet calculated it. Let's get on with this.

While we have in the register OCR1A is the number 0b1000000000000000, which corresponds to the decimal number 32768.

The microcontroller works for us, as we agreed, at a frequency of 8,000,000 Hz.

Divide 8,000,000 by 32768 to get approximately 244.14. It is with such a frequency in hertz that our timer will work if we do not apply a divider. That is, our numbers will change 244 times per second, so we won’t even see them. Therefore, you will need to apply a timer frequency divider. Let's choose a divisor by 256. It will just suit us, and then we will adjust exactly up to 1 Hz with a comparison number.

Here are the dividers for 1 timer

I have highlighted the divisor we need in the table. We see that we only need to set the bit CS12.

Since we have a frequency divider of 256, we will divide 8000000 by this divider, it will turn out 31250, this is what we must enter the number in TCNT. Until such a number, our timer will count to count up to 1 second. The number 31250 is 0b0111101000010010 in binary. Let's put this number in the register pair, and also apply the divisor

OCR1AH= 0b 01111010 ; //write a number to the register for comparison

OCR1AL= 0b 00010010 ;

TCCR1B|= (1<< CS12 ); //set the divisor.

That's all with this feature.

Now the next function is the timer interrupt handler by coincidence. She writes like this

ISR( TIMER1_COMPA_vect)

{

}

And the body of this function will be executed by itself upon the occurrence of a coincidence of numbers.

We need a variable. Let's declare it globally, at the beginning of the file

#include

//———————————————

unsignedchari;

//———————————————

Accordingly, we will remove the same variable from the code in the main () function

intmain( void)

unsignedchari;

We also comment out all the code in an infinite loop. His role will now be played by a timer, and, I think, he will cope with this no worse, and even better, without interfering with "anyone".

while(1)

{

// for(i=0;i<10;i++)

// {

// while (butstate==0)

// {

// if (!(PINB&0b00000001))

// {

// if(butcount< 5)

// {

// butcount++;

// }

// else

// {

// i=0;

// butstate=1;

// }

// }

// else

// {

// if(butcount > 0)

// {

// butcount-;

// }

// else

// {

// butstate=1;

// }

// }

// }

// segchar(i);

// _delay_ms(500);

//butstate=0;

// }

Now, actually, the body of the handler function. Here we will call the segchar function. Then we will increase by 1 variable i. And so that it does not go beyond a single digit, we will reset it under this condition

ISR( TIMER1_COMPA_vect)

if( i>9) i=0;

segchar( i);

i++;

Now let's fix the code at the beginning of the main() function a little. Port D, which is responsible for the state of the segments, we will score with ones, so that when turned on, the indicator does not light up, since it is with a common anode. Then we put the number 0 in the global variable i here, just for the sake of order. In general, as a rule, at start in uninitialized variables, and so always zeros. But we still initialize it. And, most importantly, for the timer interrupt to work, it is not enough to include it in the timer initialization. Also, in general, for all interrupts to work, global interrupts must be enabled. There is a special function for this. sei() - Set Interrupt.

Now the code will be like this

DDRB= 0x00;

PORTD= 0b 11111111 ;

PORTB= 0b00000001;

i=0;

sei();

while(1)

Also, we must also include the interrupt library file at the beginning of the file

#include

#include

#include

Also, we won't need variables for the button yet, since we won't work with the button today. Let's comment them out

intmain( void)

//unsigned char butcount=0, butstate=0;

timer_ini();

Let's collect our code and check its performance first in Proteus. If everything works fine, then we will also check in the live scheme

Everything works for us. Excellent!

Here is such a stopwatch. But since we don’t even have a quartz resonator, this stopwatch cannot be called accurate.

However, today we have learned a lot. We learned about interrupts, also learned how to handle them, learned how to work with timers, configure several new microcontroller registers, before that we only worked with port registers. Also, due to all this, we significantly unloaded the arithmetic-logical unit of our microcontroller.

Watch VIDEO LESSON

Post Views: 17 258

Several interrupts may be required during the implementation of the project, but if each of them has the maximum priority, then in fact none of the functions will have it. For the same reason, it is not recommended to use more than a dozen interrupts.

Handlers should only be applied to those processes that have the maximum sensitivity to time intervals. Do not forget that while the program is in the interrupt handler, all other interrupts are disabled. A large number of interruptions leads to a deterioration in their response.

At the moment when one interrupt is active, and the rest are disabled, there are two important nuances that the circuit designer must take into account. First, the interruption time should be as short as possible.

This will ensure that all other scheduled interrupts are not missed. Secondly, when processing an interrupt, the program code should not require activity from other interrupts. If this is not prevented, the program will simply freeze.

Do not use lengthy processing loop() , it is better to develop code for an interrupt handler with the variable set to volatile. It will tell the program that no further processing is needed.

If the function call Update() is necessary, you will first need to check the state variable. This will determine if further processing is necessary.

Before you start configuring the timer, you should check the code. Anduino timers should be attributed to limited resources, because there are only three of them, and they are used to perform a variety of functions. If you get confused with the use of timers, then a number of operations may simply stop working.

What functions does a particular timer operate on?

For the Arduino Uno microcontroller, each of the three timers has its own operations.

So Timer0 is responsible for PWM on the fifth and sixth pin, functions millis() , micros() , delay() .

Another timer - timer1, used with PWM on the ninth and tenth pin, with libraries WaveHC and Servo.

Timer2 works with PWM on pins 11 and 13, as well as with tone.

The circuit designer must take care of the safe use of the shared data. After all, an interrupt stops all processor operations for a millisecond, and data exchange between loop() and interrupt handlers must be permanent. A situation may arise when the compiler, in order to achieve its maximum performance, starts optimizing the code.

The result of this process will be to save a copy of the main code variables in the register, which will ensure the maximum speed of access to them.

The downside of this process can be that the actual values ​​are replaced with saved copies, which can lead to loss of functionality.

To prevent this from happening, you need to use a variable voltatile , which will help prevent unnecessary optimizations. When using large arrays that require cycles for updates, you need to disable interrupts at the time of these updates.

In fact, the microcontroller timer is a digital counter, only "fancy". A clock signal is applied to the input of the counter, on the basis of which the counter increases its value. When events occur - a counter overflow or a coincidence of its value with a given one - an interrupt request is generated.

Let's see how to use the T0 timer in Normal mode. In this mode, the timer counts from some initial value of the counting register to the maximum possible value (up to 255 or 0xFF). When the timer T0 counts to the maximum, then in the next cycle of the timer, the counting register TCNT0 overflows - it is reset to zero and the TOV0 flag is set. If interrupts are enabled globally in the program (flag I of the SREG register) and timer interrupt T0 on overflow (flag TOIE0 of the TIMSK register), then the microcontroller will call the appropriate handler. If the value of the counting register matches the comparison register OCR0, then the OCF0 flag will be set and, if the match event interrupt is enabled, its handler will be launched.

Timer T0 in Normal mode

Let's consider a practical task - we need to poll the button every 20 ms. Microcontroller frequency 8 MHz, ATmega16 microcontroller.

The first thing to do is to decide on the choice of the timer prescaler factor and calculate the initial value for the TCNT0 counting register.

The timer T0 can be clocked from the internal clock signal of the microcontroller or from an external one, which is fed to the T0 pin. When operating from an internal clock signal, the user can select the frequency division factors of this signal. Timer T0 has five possible prescaler factors - 1, 8, 64, 256, 1024.

To solve the problem, I argue as follows. If one cycle of the T0 timer had a period of 1 ms, then it would suit me. 20 cycles gives 20 ms. What is the timer prescaler ratio to get close to 1 ms clock period? You can count.

Microcontroller clock frequency Fcpu = 8000000 Hz
Microcontroller clock period Tcpu = 1/Fcpu
Timer clock period T0 is Tt0 = (1/Fcpu)/k = k/Fcpu

With k = 1024, the timer clock period T0 will be equal to Tt0 = 1024/8000000 = 0.128 ms

This is the maximum timer clock period we can get under our conditions (Fcpu = 8 MHz). With smaller coefficients, the period will be even shorter.

Well, let's say one timer cycle is 0.128 ms, will the counting register have enough capacity to count this time interval and how many cycles will it take? We divide the required time interval (20 ms) by the duration of one timer cycle and get the answer.

n = t/Tto = 20 ms/ 0.128 ms = 156.25

Rounded up to a whole, we get 156 cycles. This is less than 255 (the maximum value of the counting register), so the capacity of the counting register TCNT0 is enough.

The initial value for the counting register TCNT0 is calculated as the difference between the maximum number of timer cycles T0 and the required one, that is, 256 - 156 = 100. (256 is the maximum number of time intervals that any 8-bit timer can count.)

I think it is now clear how to calculate the initial value of TCNT0 for the Normal mode:

Calculate the period of one timer cycle Tt0 = k/Fcpu,
- calculate the required number of cycles for a given interval n = t/Tto,
- calculate the initial value for the counting register TCNT0 = 256 - n.

You can automate this procedure using macros. For example, like this:

#define F_CPU 8000000UL
#define TIME_MS(time, k) (256L - ((time)*(F_CPU))/(1000L*(k)))

But with such a macro, you need to be on the lookout, for certain values ​​of time and k, errors may occur.

Now let's move on to the code. To use the T0 timer (and any other too), you need to configure (initialize) it and describe the interrupt handler (if they are used).

Timer initialization consists of the following steps:

stop timer,
- setting the Normal mode to TCCR0 without starting,
- setting the initial value of TCNT0,
- reset flags in the TIFR register,
- enable overflow interrupt in TIMSK,
- setting the prescaler to TCCR0, that is, the start of the timer

This sequence is subject to variation.

For our task, the initialization code will look like this:


/*value for counting register*/
#define T_POLL 100

TCCR0 = 0;
TCCR0 = (0<TCNT0=T_POLL;
TIFR = (1<TIMSK |= (1<TCCR0 |= (1<

The second line of initialization is, in fact, useless, it is added for clarity. To clearly see which timer mode is being set.

The interrupt flags in the TIFR register are cleared by writing a 1 to the appropriate bit. This operation must be performed precisely by rewriting the register, and not using a bitwise OR. And that's why.

Let's say the TIFR register has two interrupt flags, TOV1 and TOV0, set. TOV0 we need to reset. When setting the required bit with ORthe following thing happens.


//TIFR is 0b00000101
//flags set TOV1 and TOV0
//code is executed TIFR |= (1<
//TIFR is copied to R16
IN R16, 0x38

//in R16 bit TOV0 is set
// even though it's already installed
ORI R16, 0x02

//R16 equal to 0b00000101 is written to the TIFR register
OUT 0x38, R16

As a result, both flags are reset, and we wanted to reset one.

We continue.

The syntax for describing interrupt handlers is slightly different for different compilers. For IAR, the T0 timer interrupt handler for the overflow event will look like this:



{
TCNT0=T_POLL;

/*button poll should be here*/

TIMER0_OVF_vect is the address of the overflow event interrupt vector. It is taken from the header files on the microcontroller. In this case, I took it from the iom16.h file.

The first line of the handler (TCNT0 = T_POLL;) rewrites the counting register, then sets its initial value. If this is not done, the timer will continue counting from 0. Rewriting the counting register must be done at the beginning of the interrupt handler.

All the code for our task will look something like this. (The code is for IAR. For other compilers, you need to change the header files and the interrupt handler.)

#include
#include
#include

#define T_POLL 100

int main(void)
{
/*timer initialization*/

TCCR0 = 0;
TCCR0 = (0<TCNT0=T_POLL;
TIFR |= (1<TIMSK |= (1<TCCR0 |= (1<

/*initialization of the rest of the peripherals*/
DDRB |= (1<

enable_interrupt();
while(1);

/* interrupt handler T0
overflow event*/
#pragma vector = TIMER0_OVF_vect
__interrupt void TimerT0Ovf(void)
{
/*rewrite counting register*/
TCNT0=T_POLL;

/*polling the button*/

/*invert PB0 for debugging*/
PORTB ^= (1<

OC0 output control

In Normal mode, the timer T0 can change the state of the OC0 pin when the count register and the comparison register match. And even without interruptions. The control options are defined by the COM01 and COM00 bits of the TCCR0 register.

Here is an example of a program that generates a square wave on pin OC0.

#include
#include

int main(void)
{
/*initialize timer T0*/

TCCR0 = 0;
TCCR0 = (0<TCNT0 = 0;
OCR0 = 0;
TIMSK = 0;
TCCR0 |= (1<

/*initialize OC0*/
DDRB |= (1<

While(1);
return 0;
}

The output OS0 will change its state to the opposite at zero value of the counting register.

A few points about using the timer

The timer interrupt handler (and any other peripheral) should be made as short as possible.

If the calculated value for the counting register (or comparison register) is rounded off, then the time interval will be counted by the timer with an error.

And the last. It may happen that the processing of the timer interrupt is delayed (for example, due to the fault of another handler) and the TCNT0 register has already counted several cycles. If you simply overwrite the value of TCNT0, then the next interrupt will be called later than necessary. It turns out that the previous (delayed) and new interrupts will not withstand the required interval.

This situation can be smoothed out by overwriting the counting register like this:

TCNT0 = TCNT0 + startValue;

Adding the current value of the counting register to the initialized one will take into account these extra cycles.True, there is one BUT! For large values ​​of startValue, the addition operation may cause the counter register to overflow.

For example, startValue = 250, and the timer managed to count up to 10. Then the addition operation will lead to the following result:

10 + 250 = 260

We take 8 bits from 260, we get 4. 4 will be written to TCNT0.

Share