
Often, when a microcontroller device is in operation, there is a need to count the “anthropomorphic” time - how many fractions of a second the LED lights up, the maximum time interval double-click, etc. In general, count not only nano- and microseconds, but tens of milliseconds, or even seconds , minutes and even hours (I'm afraid to say about the day ...).
At the same time, microcontrollers often need to deal with microseconds at the same time — impulse periods, debounce, and so on.
There are also devices that operate continuously for many hours and even days - aviation technology, automotive, downhole devices (there we are sometimes talking about continuous work for several days). In these cases, it is not allowed to overflow timers and 8-bit variables.
I would like to combine all this into one elegant and universal solution - to have a means of measuring time with microsecond accuracy that does not overflow for several days.
Why not? I suffered for some time and gave birth to a solution for AVR 8-bit microcontrollers. For this, I used an 8-bit timer-counter and a 4-byte variable. I am not working with PICs and AT89 now, but I am not friends with other embedded platforms. However, if readers help, I will do it for them.
Advantages - the code is extremely repeatable (I already do the 5th device with it); simplicity in work (interruptions for client part of work are not used); the client part of the code is conditionally platform-independent; in the interrupt - one operation of summation (but, truth, for 4-hbyte value); no external device - real-time timer.
I found one drawback - one such busy and always necessary timer is busy ...
The article will be interesting first of all for beginners - I did not discover America here.
Theory
So, I have an Atmega16A based device with 12MHz quartz. We take its timer counter 0. This is an eight-bit timer - that's enough for us. Why? We consider:
- take 12 MHz from quartz and take the division factor by 8 - we get a frequency of 1500 KHz;
- We take the CTC mode (reset by coincidence) and set the interrupt to match 150 - we get the interrupt response frequency 10 KHz;
- on this very interrupt, increment the variable (we get an increment every 0.1 millisecond);
- if it is an unsigned 32-bit value, then it will overflow approximately after
- 429496729.6 milliseconds;
- 42949.7 seconds;
- 7158.3 minutes;
- 119.3 hours;
- 4.97 days.
In other words, such a solution creates for me a timer with an accuracy of 0.1 millisecond for (almost) 5 days (it is necessary here, however, to take into account that real quartz has an error - more on this later). And if you also analyze the value of the actual timer 0 - it is incremented every 2/3 microseconds - then you can get a counter with an accuracy of 0.67 microseconds.
Enough? I - for the eyes. Using a 0.1 millisecond counter, I’m in my projects:
- I consider the duration of the glow and the pauses between them of the LEDs;
- I take into account timeouts when working with UART, USB;
- I ask all sorts of situations in the test equipment - complex space-time combinations;
- I maintain specified periods of time when polling the ADC and other sensors;
- I inform the computer of my (device) work time and with the specified time interval I transmit information;
- taking into account the counter to microseconds, I carry out anti-bounce control when pressing keys, the analysis of pulses in long lines.
And all this calmly intercepts with ONE ATmega16 CONTROLLER! And this is not an Assembler, but a cross-platform Xi! And no external real time counter!
Not bad, huh?
Setup for AVR
How to do it all in AVR?
First of all, we set up an external variable, which I call “DecMilliSecond”:
As @ no-smoking correctly noted, this variable must be volatile so that its compiler does not try to optimize.
I initialize this variable in a function:
dmsec = 0;
Next, I set the timer operation mode 0:
At the same time in some MCU_init.h I declare everything that is necessary:
Well and further, when it is possible, I allow interruptions:
#asm ("SEI")
It remains to describe the interruption. It is simpler than all the previous ones:
#include <mega16.h> interrupt [TIM0_COMP] Timer0_Compare (void) { ++dmsec; }
All, the timer is described, configured and running!
')
PIC Setup
This is what my dear PICs prompted me:
On peaks, this is easily repeated using the Timer2 module. It is in it that there is a similar interrupt function by coincidence.
PR2 = 75 - the value at which the timer is reset and generates an interrupt
T2CON.T2CKPS = 2 - Prescaler 1:16
T2CON.T2OUTPS = 0 - no postscaller
T2CON.TMR2ON = on - the timer is on
IPR1.TMR2IP = 1 - high priority interrupt
PIR1.TMR2IF = off - reset interrupt flag
PIE1.TMR2IE = on - enable interrupt by coincidence TMR2 and PR2
INTCON.GIE = on - enable interrupt handling
As you can see, the prescaler is 2 times more here, because PR2 is 2 times less.
These settings will generate interrupts with a frequency of 10 kHz at a system frequency of 48 MHz (the timer goes Fosc / 4) - the standard frequency for USB Full Speed.
Using
The code for the client of this timer is obtained cross-platform (if not to refer to the value of timer 0 in AVR).
Here is the USB sharing code snippet:
#include "main.h"
The macro functions RECEIVE_BYTE, RECEIVE_WORD, RECEIVE_DWORD implement reading procedures taking into account timeout for this phase of the exchange. In the end, if something is hanging on the other side, the microcontroller will not hibernate. Please note - WatchDog is not needed! And all thanks to the variable / constant max_USB_timeout, which sets the timeout to within 0.1 millisecond.
Similarly, the analysis of "silence in the air" variable next_USB_timeout. This allows the microcontroller to 1) find out that the computer has disappeared somewhere, 2) to signal something about this (in my case, the “error” LED lights up). Constant / variable MaxSilence_PC_DEV allows you to vary the concept of "silence" in the widest limits - from a fraction of a millisecond to several days.
All other moments are implemented in the same way.
If you need to use the microsecond counter, then the comparison function appears there:
#define GetUSec(A,B) { #asm ("CLI"); A = dmsec; B = TCNT0; #asm ("SEI"); }
The previous moment of time is transferred to the function - the previous value of dmsec and timer 0.
First, we stop interrupts with the GetUSec macro so that the dmsec value and the counter value do not deteriorate at the time of copying. And copy the current time.
Next, we bring the time difference to the 2/3 microsecond format, taking into account the overflow.
Well, we return this time.
And then we use it in the usual if to control debounce and other activities. Just do not forget to also suspend interruptions while checking the current time point - or rather, use the GetUSec macro.
results
This timer turned out to be a highly convenient solution for me. I think it will be useful to you. And I applied it in the following projects:
- Switchboard fencing situations . This is a hefty half a meter half board with three controllers - the ATmega128 as the central one and the ATmega64 as the two auxiliary ones (right and left sides). There is no galvanic connection between the three controllers and their components - power supply based on ionistors, connection through optocouplers. The central controller charges the groups of one ionistors and feeds both sides of the other ionistors at this time. Here we had to make a multistep switching algorithm for all of this in order to minimize interconnection. In particular, we are talking about the coordinated work of 8 relays - here the timers for 3.3 ms work (guaranteed response time of the relay). Well, in fact, both sides control 10 relays and more than 100 multiplexers from the floor. All this farm works with clearly defined time characteristics (with an accuracy of 1 msec, the maximum duration is 6 seconds). Well, in the end, banal timeout for USB, UART.
- Depth sensor Here I solve another problem (project in work). There are two conductors (multimeter), which set the situation “upward shift by 1 cm” and “downward shift by 1 cm”. There are many ways to set directions. In any case, these are certain combinations of pulses. With the help of this timer, I define bounce, the duration of a steady impulse. The maximum permissible chatter time is set from the computer (10 microseconds are enough here), debounce, minimum / maximum pulse duration. Well, there is a debug mode - the sensor becomes a logic analyzer. This allows you to debug the line and adjust the coefficients. Well, again timeout, LEDs.
- Analog signal sensor . Banal 8-channel ADC. Here I use a timer to maintain the necessary pauses.
Dear users from other platforms can prompt me the initialization code of the corresponding timer, as well as the rules for access to it - I'll add it here. It is possible that for other platforms it will be necessary to pick up other times. But in any case, it should be something within a few microseconds for the timer itself and something multiple of 100 microseconds for the counter variable. For, as it turned out, sometimes one millisecond is not enough.
UPDA few words about the stability of quartz resonators
As the
nerudo corrected me absolutely exactly in the comments, it can be counted for almost 5 days with a step of 2/3 microseconds, but the error of these calculations is not zero ...
Take that quartz HC49 / S at 12 MHz that I use. The manufacturer KSS declared accuracy + -15 ... + -50 x 1e-6 - this is the same
ppm . If I am not mistaken, this means that in 1 second an error of 15 microseconds runs through. So, in 4.97 days we will get an error of 268 milliseconds. If we take quartz more abruptly - say, with 1 ppm, then we get 18 milliseconds in the same time.
Not a fountain on one side. On the other hand, we do not make a high-end chronometer! At least, I do not set myself such a task. We must take into account this fact.
We faced this problem - how to achieve an accuracy of 1 millisecond in 45 minutes between two separated devices. But about it already another time.