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.


    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:
    1. we take 12 MHz from quartz and take the division coefficient by 8 - we get a frequency of 1500 KHz;
    2. 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;
    3. increment the variable on this very interrupt (an increment every 0.1 milliseconds is obtained);
    4. 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.

    Also popular now: