Beginner: a counter on the microcontroller with a step of 2/3 microseconds and an overflow of several days
Often when working with a microcontroller device, 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 also tens of milliseconds, and 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 - pulse periods, anti-bounce waiting, etc.
There are also devices that work continuously for many hours or even days - aircraft, automobiles, downhole devices (there it is sometimes about continuous work for several days). In these cases, overflow of timers and 8-bit variables is unacceptable.
I would like to combine all this into one elegant and universal solution - to have a means of measuring time accurate to microseconds, not overflowing for several days.
Why not? I suffered for a while and gave birth to a solution for 8-bit AVR microcontrollers. To do this, I used an 8-bit timer counter and a 4-byte variable. I am not working with PICs and AT89, and I am not friends with other embedded platforms. However, if readers help, I will do it for them.
Advantages - the code is highly repeatable (I already do the 5th device with it); simplicity in work (interruptions for a client part of work are not used); the client part of the code is conditionally platform-independent; in interruption - one operation of summation (but, however, for a 4-byte value); no external device - real-time timer.
I found one drawback - one such useful and always needed timer is busy ... The
article will be interesting first of all to beginners - I have not discovered America here.
So, I have at my disposal a device based on Atmega16A with 12MHz quartz. We take its timer counter 0. This is an eight-digit timer - that's enough for us. Why? We consider:
In other words, such a solution creates a timer for me with an accuracy of 0.1 milliseconds for (almost) 5 days (it’s true, however, that real quartz has an error - more on that later). And if you also analyze the value of the timer 0 itself - it is incremented every 2/3 microseconds - you can get a counter with an accuracy of 0.67 microseconds.
Enough? For me - for the eyes. Using a 0.1 millisecond counter, in my projects:
And all this calmly intermeddle in ONE ATmega16 CONTROLLER! And this is not Assembler, but cross-platform C! And no external real time counter!
Not bad, huh?
How to do it all in AVR?
First of all, we start an external variable that I call “DeciMilliSecond”:
As @ no-smoking correctly noted, this variable must be volatile so that its compiler does not try to optimize.
I do the initialization of this variable in the function:
Next, I set the operation mode of timer 0:
At the same time, in some MCU_init.h I declare everything that is needed:
Well and further, when possible, I allow interruptions:
It remains to describe the interruption. This is simpler than all the previous:
Everything, the timer is described, configured and running!
Here's what the respected PIC fans suggested to me:
At 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 will reset and generate an interrupt
T2CON.T2CKPS = 2 - prescaler 1:16
T2CON.T2OUTPS = 0 - without the post-
scaler T2CON.TMR2ON = on - the timer is on
IPR1.TMR2IP = 1 - high priority interrupt
PIR1. TMR2IF = off - reset the interrupt flag
PIE1.TMR2IE = on - turn on the interrupt by coincidence TMR2 and PR2
INTCON.GIE = on - turn on the interrupt handling
As you can see, the prescaler is 2 times bigger here, because PR2 is 2 times smaller.
These settings will generate interruptions with a frequency of 10 kHz at a system frequency of 48 MHz (Fosc / 4 goes to the timer) - the standard frequency for USB Full Speed.
The code for the client of this timer is cross-platform (except for accessing the value of timer 0 in AVR).
Here is a snippet of the USB sharing code:
The macro functions RECEIVE_BYTE, RECEIVE_WORD, RECEIVE_DWORD implement read procedures taking into account the timeout for a given exchange phase. As a result, if something hung on the other side, the microcontroller will not go into hibernation. Please note - WatchDog was not needed! And all thanks to the variable / constant max_USB_timeout, which sets the timeout with an accuracy of 0.1 milliseconds.
Similarly, the analysis of "silence on the air" of the variable next_USB_timeout is implemented. This allows the microcontroller 1) to find out that the computer has disappeared somewhere, 2) to somehow signal it (in my case, the “error” LED lights up). The constant / variable MaxSilence_PC_DEV allows you to vary the concept of "silence" in the widest range - from a fraction of a millisecond to several days.
Similarly, all other moments are realized.
If you need to use a microsecond counter, then a comparison function appears:
The previous moment of time is passed to the function - the previous value of dmsec and timer 0.
First, we stop the interrupts with the GetUSec macro so that the value of dmsec and the counter does not deteriorate at the time of copying. And copy the current time.
Next, we present the time difference to the 2/3 microsecond format, taking into account overflow.
Well, we return this time.
And then we use it in the usual if to control anti-bounce and other activities. Just remember to also suspend interrupts when you pinpoint the current point in time - or rather, use the GetUSec macro.
This timer turned out to be an extremely convenient solution for me. I think he will come in handy. And I applied it in my following projects:
Dear Habrausers from other platforms can tell me the initialization code for the corresponding timer, as well as the rules for accessing it - I'll add this 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.
UPD
As nerudo absolutely corrected me in the comments , you can read it for almost 5 days with a step of 2/3 microseconds, but the error in these calculations is not zero ...
Take the very same HC49 / S quartz at 12 MHz that I use. KSS manufacturer claimed accuracy + -15 ... + -50 x 1e-6 is the same ppm . If I am not mistaken, this means that in 1 second an error of 15 microseconds will come. So, in 4.97 days we get an error of 268 milliseconds. If we take quartz abruptly - say, with 1 ppm, then we get 18 milliseconds in the same time.
Not a fountain - on the one hand. On the other hand - we do not make a high-end chronometer! At least I don’t set myself such a task. This fact must be taken into account.
We also faced this problem - how to achieve accuracy of 1 millisecond in 45 minutes between two spaced devices. But about this another time.
At the same time, microcontrollers often need to deal with microseconds at the same time - pulse periods, anti-bounce waiting, etc.
There are also devices that work continuously for many hours or even days - aircraft, automobiles, downhole devices (there it is sometimes about continuous work for several days). In these cases, overflow of timers and 8-bit variables is unacceptable.
I would like to combine all this into one elegant and universal solution - to have a means of measuring time accurate to microseconds, not overflowing for several days.
Why not? I suffered for a while and gave birth to a solution for 8-bit AVR microcontrollers. To do this, I used an 8-bit timer counter and a 4-byte variable. I am not working with PICs and AT89, and I am not friends with other embedded platforms. However, if readers help, I will do it for them.
Advantages - the code is highly repeatable (I already do the 5th device with it); simplicity in work (interruptions for a client part of work are not used); the client part of the code is conditionally platform-independent; in interruption - one operation of summation (but, however, for a 4-byte value); no external device - real-time timer.
I found one drawback - one such useful and always needed timer is busy ... The
article will be interesting first of all to beginners - I have not discovered America here.
Theory
So, I have at my disposal a device based on Atmega16A with 12MHz quartz. We take its timer counter 0. This is an eight-digit timer - that's enough for us. Why? We consider:
- we take 12 MHz from quartz and take the division coefficient by 8 - we get a frequency of 1500 KHz;
- we take the CTC mode (reset when coincident) and set the interrupt to coincide with 150 - we get the interrupt response frequency of 10 KHz;
- increment the variable on this very interrupt (an increment every 0.1 milliseconds is obtained);
- 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 a timer for me with an accuracy of 0.1 milliseconds for (almost) 5 days (it’s true, however, that real quartz has an error - more on that later). And if you also analyze the value of the timer 0 itself - it is incremented every 2/3 microseconds - you can get a counter with an accuracy of 0.67 microseconds.
Enough? For me - for the eyes. Using a 0.1 millisecond counter, in my projects:
- I consider the duration of the glow and pauses between the LEDs;
- I take into account timeouts when working with UART, USB;
- I ask all kinds of situations in test equipment - complex spatio-temporal combinations;
- I withstand the specified intervals during the survey of the ADC and other sensors;
- I inform the computer the time of its (device) operation and transmit the information at a given time interval;
- taking into account the counter to the microsecond, I carry out anti-bounce control at the touch of a button, analysis of pulses in extended lines.
And all this calmly intermeddle in ONE ATmega16 CONTROLLER! And this is not Assembler, but cross-platform C! And no external real time counter!
Not bad, huh?
Setup for AVR
How to do it all in AVR?
First of all, we start an external variable that I call “DeciMilliSecond”:
// в main.h
typedef unsigned long dword; // беззнаковое 32х-битное целое
extern volatile dword dmsec; // 0.1msec
// в main.c
volatile dword dmsec;
As @ no-smoking correctly noted, this variable must be volatile so that its compiler does not try to optimize.
I do the initialization of this variable in the function:
dmsec = 0;
Next, I set the operation mode of timer 0:
// . таймер 0 – 0.1msec
Timer0_Mode (TIMER_Mode_CTC | TIMER0_Clk_8);
Timer0_Cntr (149);
Timer_Int (Timer0_Cmp);
At the same time, in some MCU_init.h I declare everything that is needed:
// в mcu_init.h
#include
// . TIMSK
#define Timer0_Cmp (1 << 1) // совпадение таймера 0
// . TCCRn
#define WGM1 (1 << 3)
#define CS1 (1 << 1)
// . источник сигнала для таймера 0
#define TIMER0_Clk_8 CS1 // предделитель 8
// . режим работы таймера
#define TIMER_Mode_CTC WGM1 // CTC (сброс при совпадении)
// . настройка таймера
#define Timer_Int(Mode) TIMSK = (Mode)
#define Timer0_Mode(Mode) TCCR0 = (Mode)
#define Timer0_Cntr(Cntr) OCR0 = (Cntr)
Well and further, when possible, I allow interruptions:
#asm ("SEI")
It remains to describe the interruption. This is simpler than all the previous:
#include
interrupt [TIM0_COMP] Timer0_Compare (void)
{
++dmsec;
}
Everything, the timer is described, configured and running!
Setup for PIC
Here's what the respected PIC fans suggested to me:
At 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 will reset and generate an interrupt
T2CON.T2CKPS = 2 - prescaler 1:16
T2CON.T2OUTPS = 0 - without the post-
scaler T2CON.TMR2ON = on - the timer is on
IPR1.TMR2IP = 1 - high priority interrupt
PIR1. TMR2IF = off - reset the interrupt flag
PIE1.TMR2IE = on - turn on the interrupt by coincidence TMR2 and PR2
INTCON.GIE = on - turn on the interrupt handling
As you can see, the prescaler is 2 times bigger here, because PR2 is 2 times smaller.
These settings will generate interruptions with a frequency of 10 kHz at a system frequency of 48 MHz (Fosc / 4 goes to the timer) - the standard frequency for USB Full Speed.
Using
The code for the client of this timer is cross-platform (except for accessing the value of timer 0 in AVR).
Here is a snippet of the USB sharing code:
#include "main.h" // тут переменная dmsec, next_USB_timeout
#include "FT245R.h" // тут функции работы с модулем USB
#include "..\Protocol.h" // тут протокол обмена микроконтроллер - компьютер
// **
// ** Анализ пакетов по USB
// **
void AnalyzeUSB (void)
{
#define RECEIVE_BYTE(B) while (!FT245R_IsToRead)\
{ if (dmsec > end_analyze) return; }\
B = FT245_ReadByte ();
#define RECEIVE_WORD(W) // аналогично для 2х байт
#define RECEIVE_DWORD(W) // аналогично для 4х байт
dword end_analyze, d;
NewAnalyze:
if (!FT245R_IsToRead) // нет пакетов?
return;
end_analyze = dmsec + max_USB_timeout; // timeout для текущего анализа
next_USB_timeout = dmsec + MaxSilence_PC_DEV; // timeout для общего обмена
RECEIVE_BYTE (b) // заголовок пакета
switch (b)
{
case SetFullState:
RECEIVE_DWORD (d); // читаем слово
is_initialized = 1; // обрабатываем
ChangeIndicator ();
break;
} // switch (pack)
goto NewAnalyze;
#undef RECEIVE_BYTE // отменяем #define
#undef RECEIVE_WORD
#undef RECEIVE_DWORD
}
The macro functions RECEIVE_BYTE, RECEIVE_WORD, RECEIVE_DWORD implement read procedures taking into account the timeout for a given exchange phase. As a result, if something hung on the other side, the microcontroller will not go into hibernation. Please note - WatchDog was not needed! And all thanks to the variable / constant max_USB_timeout, which sets the timeout with an accuracy of 0.1 milliseconds.
Similarly, the analysis of "silence on the air" of the variable next_USB_timeout is implemented. This allows the microcontroller 1) to find out that the computer has disappeared somewhere, 2) to somehow signal it (in my case, the “error” LED lights up). The constant / variable MaxSilence_PC_DEV allows you to vary the concept of "silence" in the widest range - from a fraction of a millisecond to several days.
Similarly, all other moments are realized.
If you need to use a microsecond counter, then a comparison function appears:
#define GetUSec(A,B) { #asm ("CLI"); A = dmsec; B = TCNT0; #asm ("SEI"); }
// **
// ** Разница во времени между событиями с точностью до 2/3usec
// **
dword Difference (dword prev_dmsec, byte prev_usec)
{
dword cur_dmsec;
byte cur_usec;
dword dif;
// . засекаем текущее время
GetUSec (cur_dmsec, cur_usec);
// вычисляем разницу
dif = cur_dmsec - prev_dmsec;
dif <<= 8;
if (cur_usec < prev_usec)
dif += 255 + (dword) cur_usec - prev_usec;
else
dif += cur_usec - prev_usec;
return dif;
}
The previous moment of time is passed to the function - the previous value of dmsec and timer 0.
First, we stop the interrupts with the GetUSec macro so that the value of dmsec and the counter does not deteriorate at the time of copying. And copy the current time.
Next, we present the time difference to the 2/3 microsecond format, taking into account overflow.
Well, we return this time.
And then we use it in the usual if to control anti-bounce and other activities. Just remember to also suspend interrupts when you pinpoint the current point in time - or rather, use the GetUSec macro.
results
This timer turned out to be an extremely convenient solution for me. I think he will come in handy. And I applied it in my following projects:
- Fencing Switch. This is a hefty half a meter board with three controllers - ATmega128 as the center and ATmega64 as two auxiliary (right and left sides). There is no galvanic connection between the three controllers and their components - power supply based on ionistors, communication via optocouplers. The central controller charges groups of some ionistors and feeds both sides of the other ionistors at this time. Here we had to make a multi-stage switching algorithm for all this in order to minimize the relationship. In particular, we are talking about the coordinated work of 8 relays - here timers work for 3.3ms (guaranteed relay response time). Well, actually, both sides control 10 relays and even with hundreds of multiplexers. All this farm works with clearly defined time characteristics (with an accuracy of 1 ms, the maximum duration is 6 seconds). Well,
- Depth sensor . Here I solve another problem (project in work). There are two conductors (multi-meter) that specify the situation “shift up by 1 cm” and “shift down by 1 cm”. There are many ways to specify a direction. In any case, these are certain combinations of impulses. With this timer, I determine the bounce, the duration of a steady pulse. From the computer, the maximum allowable bounce time is set (10 microseconds are enough here), anti-bounce wait, minimum / maximum pulse duration. Well, there is a debugging mode - the sensor becomes a logical analyzer. This allows you to debug the operation of the line and adjust the coefficients. Well, again, timeout, LEDs.
- Sensor of analog signals . Trite 8-channel ADC. Here I use a timer to maintain the necessary pauses.
Dear Habrausers from other platforms can tell me the initialization code for the corresponding timer, as well as the rules for accessing it - I'll add this 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.
UPD
A few words about the stability of quartz resonators
As nerudo absolutely corrected me in the comments , you can read it for almost 5 days with a step of 2/3 microseconds, but the error in these calculations is not zero ...
Take the very same HC49 / S quartz at 12 MHz that I use. KSS manufacturer claimed accuracy + -15 ... + -50 x 1e-6 is the same ppm . If I am not mistaken, this means that in 1 second an error of 15 microseconds will come. So, in 4.97 days we get an error of 268 milliseconds. If we take quartz abruptly - say, with 1 ppm, then we get 18 milliseconds in the same time.
Not a fountain - on the one hand. On the other hand - we do not make a high-end chronometer! At least I don’t set myself such a task. This fact must be taken into account.
We also faced this problem - how to achieve accuracy of 1 millisecond in 45 minutes between two spaced devices. But about this another time.