Evaluation of methods for measuring low frequencies on the Arduino. Connectors used in the FC1100-M2 counter

Structurally, the device consists of a display formed by seven 7-segment LED indicators, a microcontroller and several transistors and resistors. The microcontroller performs all the necessary functions, so the use of any additional microcircuits is not required.

circuit diagram The device is quite simple and is shown in Figure 2. The Eagle project (schematic diagram and PCB) is available for download in the download section.

The tasks performed by the microcontroller are simple and obvious: counting the number of input pulses in 1 second and displaying the result on a 7-digit indicator. Most important point here is the master oscillator precision (time base) provided by the built-in 16-bit timer Timer1 in CTC mode. The second, 8-bit, timer-counter operates in the mode of counting the number of pulses at its input T0. Every 256 pulses cause an interrupt, the handler of which increments the value of the coefficient. When the duration of 1 s is reached with the 16-bit timer, an interrupt occurs, but in this case, the factor is multiplied by 256 (left shift by 8 bits) in the interrupt handler. The remaining number of pulses registered by the counter is added to the result of the multiplication. The resulting value is then divided into separate numbers, which are displayed on a separate indicator in the corresponding category. After that, just before exiting the interrupt handler, both counters are simultaneously reset and the measurement cycle is repeated. In "free time", the microcontroller outputs information to the indicator using the multiplexing method. In the source code of the microcontroller program, the author gave additional comments that will help to understand in detail the algorithm of the microcontroller.

Resolution and measurement accuracy

The measurement accuracy depends on the clock source for the microcontroller. By itself, the program code can introduce an error (adding one pulse) at high frequencies, but this practically does not affect the measurement result. The quartz resonator used in the device must be good quality and have minimal error. best choice there will be a resonator whose frequency is divisible by 1024, for example 16 MHz or 22.1184 MHz. To obtain a measurement range of up to 10 MHz, it is necessary to use a quartz resonator at a frequency of 21 MHz and higher (for 16 MHz, as in the diagram, the measurement range becomes slightly lower than 8 MHz). A quartz resonator at a frequency of 22.1184 MHz is ideal for our device, however, the purchase of just such a one with a minimum error for many radio amateurs will be challenging task. In this case, you can use a quartz resonator at a different frequency (for example, 25 MHz), but you must perform the master oscillator calibration procedure using an oscilloscope that supports hardware measurements and a trimmer capacitor in the quartz resonator circuit (Figure 3, 4).

In the download section, several versions of firmware for various quartz resonators are available for download, but users can compile the firmware for an existing quartz resonator on their own (see comments in the source code).

Input signal

In the general case, a signal of any shape with an amplitude of 0 ... 5 V can be applied to the input of the device, and not just rectangular pulses. You can apply a sinusoidal or triangular signal; the pulse is determined by the falling edge at 0.8 V. Please note: the input of the frequency counter is not protected against high voltage and not pulled up to power, this is a high-resistance input that does not load the circuit under test. The measurement range can be extended up to 100 MHz with a resolution of 10 Hz by using an appropriate high-speed frequency divider at the input.

Display

The device uses seven LED 7-segment indicators with a common anode as a display. If the brightness of the indicators is insufficient, you can change the value of the resistors that limit the current through the segments. However, do not forget that the value impulse current for each output of the microcontroller should not exceed 40 mA (indicators also have their own operating current, do not forget about its value). In the diagram, the author indicated the value of these resistors is 100 ohms. Insignificant zeros are suppressed when displaying the measurement result, which makes reading the readings more comfortable.

Printed circuit board

The double-sided printed circuit board has dimensions of 109 × 23 mm. IN free version The Eagle PCB Design Environment does not have seven-segment LED indicators in the component library, so they were hand-drawn by the author. As seen in the photographs (Figures 5, 6, 7) of the author's version printed circuit board additionally it is necessary to make several connections with a mounting wire. One connection on the front side of the board is power to the Vcc pin of the microcontroller (through a hole in the board). There are two more connections on the underside of the board, which are used to connect the decimal point segment pins of the indicators in digits 4 and 7 through 330 ohm resistors to ground. For in-circuit programming of the microcontroller, the author used a 6-pin connector (in the diagram, this connector is shown as a composite JP3 and JP4), located at the top of the printed circuit board. This connector does not have to be soldered to the board, the microcontroller can be programmed in any way possible.

Downloads

Schematic diagram and drawing of the printed circuit board, source code and microcontroller firmware -

main feature this frequency meter:
A highly stable TCXO (Thermo-Compensated Reference Oscillator) is adopted. The use of TCXO technology allows immediately, without preheating, to provide the declared frequency measurement accuracy.

Specifications of FC1100-M2 frequency counter:

parameter minimum norm maximum
Measuring frequency range 1 Hz. - 1100 MHz.
Frequency readout resolution from 1 to 1100 MHz - 1 kHz. -
Frequency readout resolution from 0 to 50 MHz - 1 Hz. -
Input signal level for input "A" (from 1 to 1100 MHz). 0.2 V.* 5 W.**
Input signal level for input "B" (0 to 50 MHz). 0.6V 5 V.
Update period - 1 time/sec -
Testing of quartz resonators 1 MHz - 25 MHz
Supply voltage/current consumption (Mini-USB) +5V./300mA
Frequency stability @19.2MHz, at temperature -20С...+80С 2ppm(TCXO)

Distinctive features of FC1100 and FC1100-M2 frequency meters in particular:

Highly stable reference oscillator TCXO(stability is not worse than +/-2 ppm).
Factory calibration.
Independent simultaneous measurement of two frequencies (Input "A" and Input "B").
Input "B": Provides frequency measurement resolution of 1 Hz.
Input "B" has a full-fledged analog input comparator threshold control (MAX999EUK), which makes it possible to measure, including signals noisy with harmonics, by adjusting the comparator threshold to a clean section of the periodic signal.
Input "A" allows you to remotely measure the frequency of portable VHF radios at a distance of several meters, using a short antenna.
Function of fast testing of quartz resonators from 1 to 25 MHz.
Modern TFT color display with economical backlight.
The manufacturer does not use unreliable electrolytic capacitors. Instead, modern high-quality SMD ceramic capacitors of significant capacities are used.
Unified power supply via Mini-USB connector (+5v). Mini-USB power cord - supplied.
The design of the frequency meter is optimized for integration into the flat front panel of any enclosure. Nylon insulating posts M3*8mm are supplied in the kit to provide a gap between the front panel and the printed circuit board of the frequency meter.
The manufacturer guarantees that programmed aging technologies, which are widely used in modern technology, are not used.
Made in Russia. Small batch production. Quality control at every stage of production.
The best solder pastes, non-cleaning fluxes and solders are used in the production.

Dimensions of the printed circuit board of the FC1100-M2 device: 83mm * 46mm.
Display color TFT LCD with backlight (diagonal 1.44" = 3.65cm).
* Sensitivity according to DataSheet MB501L ("Input Signal Amplitude" parameter: -4.4dBm = 135 mV@50 Ohm respectively).
** The upper limit of the input signal is limited by the dissipation power of the B5819WS protective diodes (0.2W*2 pcs).


Back side of the FC1100-M2 counter

Scheme of the comparator / shaper of the input signal 0 ... 50 MHz.

Scheme of the frequency divider of the input signal 1...1100 MHz.

Brief description of FC1100-M2 frequency counter:

The FC1100-M2 frequency meter has two separate frequency measurement channels.
Both channels of the FC1100-M2 counter operate independently and can be used to measure two different frequencies simultaneously.
In this case, both values ​​of the measured frequency are simultaneously displayed on the display.
"Input A" - (Connector type SMA-FEMALE) Designed to measure relatively high-frequency signals, from 1 MHz to 1100 MHz. The lower sensitivity threshold of this input is slightly less than 0.2 V., and the upper threshold is limited at the level of 0.5 ... 0.6 V. by protective diodes connected in anti-parallel. It makes no sense to apply significant voltages to this input, because voltages above the opening threshold of protective diodes will be limited.
The applied diodes allow dissipating power no more than 200 mW, protecting the input of the MB501L divider chip. Do not connect this input directly to the output of high power transmitters (more than 100mW). To measure the frequency of signal sources with an amplitude of more than 5 V., or a significant power, use an external voltage divider (attenuator) or a low-capacity transition capacitor (units of picofarads) connected in series. If it is necessary to measure the frequency of the transmitter - usually a short piece of wire is sufficient as an antenna, included in the frequency meter connector, and located at a short distance from the transmitter antenna, or you can use a suitable rubber band antenna from portable radios connected to the SMA connector.

"Input B" - (Connector type SMA-FEMALE) Designed to measure relatively low-frequency signals, from 1 Hz to 50 MHz. The lower sensitivity threshold of this input is lower than that of "Input A", and is 0.6 V., and the upper threshold is limited by protective diodes at a level of 5 V.
If it is necessary to measure the frequency of signals with an amplitude of more than 5 V, use an external voltage divider (attenuator). This input uses a MAX999 high speed comparator.
The input signal is applied to the non-inverting input of the comparator, and resistor R42 is also connected here, increasing the hardware hysteresis of the MAX999 comparator to a level of 0.6 V. The bias voltage is applied to the inverting input of the MAX999 comparator, from the variable resistor R35, which sets the level of operation of the comparator. When measuring the frequency of noisy signals, it is necessary to turn the knob of the variable resistor R35 to achieve stable readings of the frequency meter. The highest sensitivity of the frequency meter is realized in the middle position of the knob of the variable resistor R35. Counter-clockwise rotation - reduces, and clockwise - increases the threshold voltage of the comparator, allowing you to shift the threshold of the comparator to a noise-free section of the measured signal.

The "Control" button switches between the "Input B" frequency measurement mode and the quartz resonator testing mode.
In the mode of testing quartz resonators, it is necessary to connect the tested quartz resonator with a frequency from 1 MHz to 25 MHz to the extreme contacts of the "Quartz Test" panel. The middle contact of this panel - you can not connect, it is connected to the "common" wire of the device.

Please note that in the test mode of quartz resonators, in the absence of the tested quartz in the panel, there is a constant generation at a relatively high frequency (from 35 to 50 MHz).
Also, it should be noted that when the investigated quartz resonator is connected, the generation frequency will be slightly higher than its typical frequency (within units of kilohertz). This is determined by the parallel mode of excitation of the quartz resonator.
The testing mode of quartz resonators can be successfully used to select the same quartz resonators for ladder multi-crystal quartz filters. In this case, the main criterion for the selection of quartz resonators is the closest possible generation frequency of the selected quartz.

Connectors used in the FC1100-M2 frequency counter:

Power Supply for Frequency Counter FC1100-M2:

The FC1100-M2 frequency meter is equipped with a standard Mini-USB connector with a supply voltage of +5.0 Volts.
Current Consumption (300mA max) - Provides compatibility with most USB voltage power supplies.
Included is a cable "Mini-USB"<>"USB A", which allows you to power the frequency meter from any device that has such a connector (Personal Computer, Laptop, USB-HUB, USB Power Supply, Network Charger USB device) and so on.

For autonomous power supply of the FC1100-M2 Frequency Meter, the widely used "Power Bank" batteries with built-in Lithium-Polymer batteries, which are usually used to power equipment with USB connectors, are optimally suited. In this case, in addition to obvious convenience, as a bonus, you get galvanic isolation from the network and / or power supply, which is important.



A description is given of a frequency counter that measures the frequency up to 1 MHz, built on the basis of a board Arduino UNO. But, first of all, I want to remind you that the Arduino UNO is a small printed circuit board on which the ATMEGA328 microcontroller is located, as well as all its “wiring” necessary for its operation, including a USB programmer and a power source.

The cost of Arduino UNO ranges from 200 to 1000 rubles, depending on the place of sale. Of course, the cheapest on the radio market and the Chinese Internet mailbox "Aliexpress". A description of the Arduino UNO board, as well as software for it, and connecting to a personal computer is given in L.1, so if anyone is not in the know, be sure to read the article in L.1 first.

circuit diagram

The frequency meter circuit is shown in fig. 1. As can be seen from the diagram, a 1602A type H1 liquid crystal indicator module is connected to the digital ports D2-D7 of the Arduino UNO board.

And the input signal goes through a quite understandable amplifier-shaper on the VT1 transistor and the D1 chip to the D8 port. The input amplifier-shaper and the LCD indicator are powered by a 5V voltage regulator available on the Arduino UNO board.

Rice. 1. Schematic diagram of a frequency counter (up to 1 MHz) on the Arduino UNO platform.

But, back to the LCD indicator. The indicator is a board on which the LCD itself is installed and a circuit for its maintenance, made on two frameless microcircuits. The 1602A indicator is standard, based on the HD44780 controller. The designation 1602A actually means that it is on two lines of 16 characters per line.

The indicator was purchased on Aliexpress, found on request "HD44780" (prices from 81 rubles).

+5V power is supplied to the LCD indicator through pin 2 of its board. A common minus for pins 3 and 1. Since it is planned to only transfer information from the controller to the indicator, and not vice versa, pin 5 (RW) is connected to zero. Data on the LCD indicator will come through its pins 11-14 (pins 7-10 are not used).

Pins 15 and 16 are used to connect the LCD backlight. They are supplied with 5V. To control the LCD indicator, it was decided to use ports D2 to D7 of the Arduino UNO board. In principle, other ports are possible, but I just decided to use these.

LCD indicator connected. But it just won't work. In order for it to interact with the Arduino UNO, you need to load a subroutine into the program to control it.

Program

Such subroutines are called “libraries” for some reason, and there are many different “libraries” in the Arduino UNO software package.

Table 1. Program source code.

The HD44780 based LCD display requires the LiquidCrystal library. Therefore, the program for our frequency counter (table 1) begins by loading this library:

This line instructs the Arduino UNO to load this library. Next, you need to assign the Arduino UNO ports that will work with the LCD. As I said, I have chosen ports from D2 to D7. These ports are assigned by the string:

LiquidCrystal led(2, 3, 4, 5, 6, 7);

After that, the program proceeds to the actual operation of the frequency counter.

Among the set of functions of the Arduino UNO programming language there is such a function: pulseln , which can be translated as "input pulse". This function measures, in microseconds, the rise or fall of an input pulse. So the frequency measurement here will be through a preliminary period measurement.

Since the duration of the positive and negative half-cycles in a real input signal can differ, if we want to measure the period of the input pulses, we need to add the duration of the positive and negative half-cycles.

In the program, the duration of the positive half-cycle is designated Htime, the duration of the negative half-cycle is Ltime, and the duration of the entire period is Ttime.

Half-cycles are measured in the lines:

Htime=pulseln(8,HIGH);

Ltime=pulseln(8,LOW);

Then, the full period in the line is calculated:

Ttime=Htime+Ltime ;

The frequency calculation, given that the period value is expressed in microseconds, occurs here:

frequency=1000000/Ttime;

Then, the line of the LCD indicator is indicated (the bottom line, this is line 1), in which the result is written:

lcd.setCursor(OD) ;

And the result is written to the LCD indicator:

lcd.print("hz");

The working cycle of the frequency meter ends with the indication of the result for one second, or rather, a pause of one second, during which the display remains

measured value (time is expressed in milliseconds, so 1 sec = 1000): delay(1000);

Thus, our frequency meter, unlike the typical one, does not count pulses for a fixed time interval, but determines the frequency according to a previously measured period. In this case, the readings change every second. Which, however, is not necessary.

The period of change of indications (indication time) can be set to any value by changing the duration of the pause in the line:

like this: delay(2000);

Now the readings will change every two seconds. You can choose any other value, both less than 1000 and more, but you should not get too carried away with its decrease - an excessively frequent change of readings makes visual perception difficult. In my opinion, optimal -1 second.

Now about other details of the program.

In line:

pinMode(8, INPUT);

port 8 of the ARDUINO UNO board is assigned as a digital input. It receives pulses, the frequency of which needs to be measured.

In line:

lcd.begin(16,2);

it is indicated that the indicator is two-line, 16 characters per line.

We have a two-line indicator. One line remains free and you can write anything into it. Here, the word “frequency” is written in the top line (top line, this is line 0):

lcd.setCursor(0,0);

lcd print("frequency");

Unfortunately, the Chinese LCD indicator does not understand Russian, so you can only write in Latin letters. If you try to write in Cyrillic, for example, the word "frequency", he starts swearing vilely Chinese characters. If the input (port D8 ARDUINO UNO) does not receive a signal, the LCD will flash: "infhz". The frequency counter can be speeded up or slowed down by changing the display time as described above.

The accuracy of the frequency counter is very high, because the clock frequency of the microcontroller is stabilized quartz resonator installed on the ARDUINO UNO board. However, taking into account the specifics of the device, in particular, that it actually measures the period, not the frequency, and only calculates the frequency, the readings may float with a highly distorted input signal.

This frequency meter is designed to measure the frequency of the input signal. But there are other frequency meters, for example, tachometers, speedometers, etc., which receive impulses from some sensors, and the result should be given not in hertz, but in revolutions per minute or meters per second, kilometers per hour or other units. In this case, you can change the law for converting the measured period into a result in this line:

frequency=1000000/Ttime;

by substituting another value instead of 1000000, or generally changing the formula for calculating the result, according to the one that is necessary for a particular case of using the device. Accordingly, it will be necessary to change the unit of measurement, replacing it in the line: lcd.print("hz"); And change the title on the top line of the LCD by changing it on the line: lcd.print("frequency");

The supply voltage of the frequency meter can be from 7 to 12V. Power is supplied to the corresponding socket on the ARDUINO UNO board. The voltage may be unstabilized (the board has its own 5V regulator), but

necessarily well filtered (pulsations should be minimal). Power supply is also possible from a 5V source via a USB port, but in this case, the voltage must be not only well filtered, but also stabilized.

The input amplifier circuit consists of the actual input amplifier on the transistor VT1 and the Schmitt trigger on the elements of the D1 chip. By the way, the K561LE5 or K561LA7 chip can be replaced with any CMOS chip that has at least three inverters, respectively, by changing the circuit.

The input amplifier circuit can be done differently by applying other circuit solutions. But, in any case, there should be a Schmitt trigger, the output of which should be rectangular pulses with a swing of at least 3V and not more than 5V.

When powered by a galvanic battery, for example, "Krona" or its analogue with a voltage of 9V, in order to save power supply, you can turn off the backlight of the LCD indicator. To do this, you need to turn on the switch in the gap of its output 15 or 16, which will turn on the backlight, if necessary. If backlighting is not required at all, terminals 15 and 16 of the LCD indicator can not be connected at all.

In the stationary mode, when the device is powered from the mains supply, the backlight can be made permanently connected, as shown in the diagram in Figure 1.

When typing a program (table 1), it is not at all necessary to type what is in the lines after the “; " (semicolon). Because these are comments and explanations that do not affect the operation of the program.

Karavkin V. RK-12-16.

Literature:

1. Karavkin V. Christmas tree flasher on Arduino as a remedy for fear of microcontrollers. RK-11-2016.

Built . It allows you to measure frequencies up to 10 MHz in four automatically switchable ranges. The smallest range has a resolution of 1 Hz.

Specifications of the frequency meter

  • Band 1: 9.999 kHz, resolution 1 Hz.
  • Band 2: 99.99 kHz, resolution up to 10 Hz.
  • Band 3: 999.9 kHz, resolution up to 100 Hz.
  • Band 4: 9999 kHz, resolution up to 1 kHz.

Description of the frequency counter on the microcontroller

The Attiny2313 microcontroller is powered by an external crystal oscillator with a clock frequency of 20 MHz (this is the maximum allowed frequency). The measurement accuracy of the frequency meter is determined by the accuracy of this quartz. The minimum half-cycle length of the measured signal must be greater than the period of the crystal oscillator (this is due to the limitations of the architecture of the ATtiny2313 microcontroller). Therefore, 50 percent of the oscillator clock is 10 MHz (this is the maximum measurable frequency).

Installing fuses (in PonyProg):

The task, in fact, arose from the need to read the rotational speed of the obturator with an optical sensor mounted on a self-made cup anemometer. How many holes there are not drilled around the circumference, but when the wind is weak, the frequency from the sensor output will be a few hertz (especially if the sensor is well balanced and lightweight to reduce the starting threshold to the maximum).

Puzzled by such a question, the first thing I found out was that the standard libraries did not offer anything good in this regard. There is, of course, FreqMeasure and FreqPeriod , but I didn’t like them at first sight: they are overly complicated and, moreover, with almost completely missing documentation. To top it off, the examples attached to them simply didn’t work for me the first time (I guess why, but I didn’t bother - it’s not interesting to delve into other people’s mistakes).

I had to do it myself. Small frequencies need to be measured in a period, so the ideal end result is something like the pulseIn() function, only it measures the period rather than the pulse duration. It turned out several options, which I offer to the audience in the hope that they will be useful to someone. For each option, the limits of applicability were determined and the advantages and disadvantages were considered in comparison with each other.

Option 1: Redoing pulseInLong()

At first, I got hung up on the pulseIn () function - but is it possible to adapt it to this matter? In the bowels of the Arduino folders (in the file wiring_pulse.c) its more advanced version was found called pulseInLong (). It was introduced, as I found out, somewhere around version 1.6.5, but I did not understand how this option is better than the original function. Judging by the fact that the function is not included in the official list - either nothing, or has some unexplained limitations. But its structure seemed to me more transparent and easier to remake in the right direction. The function call looks the same as pulseIn():

Unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout)
Three conditionally-infinite loops work sequentially in the function (with a forced exit at a given timeout). In the first cycle, a transition to the state specified by the state parameter (HIGH or LOW) is expected - in order to skip the current impulse, in the middle of which we may have fallen. The second is expected to start next period(reverse edge), after which the number of microseconds at the start is determined. Finally, the third cycle is a measuring one, again a transition to the state state is expected, the difference in the number of microseconds is fixed and returned in the value of the function.

The change is that after the third cycle, the number of microseconds is not fixed, but a fourth cycle is added, identical to the second. And only after reaching again the same difference as at the start, the number of microseconds is fixed and returned in the value of the function. Thus we get the duration of the period. The test sketch with the reworked function, which I renamed to periodInLong, looks like this in full (some of the comments are left from the original):

#define Tone_PIN 12 // frequency output - see text #define IN_PIN 8 // frequency detection input volatile unsigned long ttime = 0; // Sensor response period unsigned long periodInLong(uint8_t pin, uint8_t state, unsigned long timeout) ( // cache the port and bit of the pin in order to speed up the // pulse width measuring loop and achieve finer resolution. calling // uint8_t bit = digitalPinToBitMask(pin); uint8_t port = digitalPinToPort(pin); uint8_t stateMask = (state ? bit: 0); unsigned long startMicros = micros(); // wait for any previous pulse to end while ((*portInputRegister(port) & bit) != stateMask) ( if (micros() - startMicros > timeout) return 0; ) // wait for the pulse to start while ((*portInputRegister(port) & bit) == stateMask) ( if (micros() - startMicros > timeout) return 0; ) unsigned long start = micros(); // wait for the pulse to stop while ((*portInputRegister(port) & bit) != stateMask) ( if (micros() - startMicros > timeout) return 0; ) // wait for the pulse to start while ((* portInputRegister(port) & bit) == stateMask) ( if (micros() - startMicros > timeout) return 0; ) return micros() - start; ) void setup() ( pinMode(IR_PIN, OUTPUT); //to output pinMode(IN_PIN, INPUT); //output frequency detection to input Serial.begin(9600); // tone(Tone_PIN, 1000); ) void loop () ( ttime=periodInLong(IN_PIN, LOW, 1000000); //wait 1 sec Serial.println(ttime); if (ttime!=0) (//in case the frequency is lost float f = 1000000/float(ttime ); // Calculate the frequency of the signal in Hz Serial.println(f,1);) delay(500); )
Notice the output of Tone_PIN and the commented call to the tone() function in the setup() section. This was done to check one more circumstance, which is discussed at the end of the article.

To test the operation, pin 8 (arbitrarily chosen as IN_PIN) was supplied with a signal from a self-made oscillator based on clock quartz and a 561IE16 divider counter. At its output, we get frequencies that are multiples of powers of two, from 2 to 2048 Hz, as well as 16384 Hz (and, if desired, another 32768 Hz directly from the generator).

The results of a selection of consecutive measurements for frequencies of 2, 8, 64, as well as 2048 and 16384 hertz are combined in one table in the figure (the top line is the duration in microseconds, the next line is the calculated frequency):

As we can see from these data, the method works quite satisfactorily for low frequencies (below about 100 hertz), but "rattles" at high frequencies. This is normal, if you remember that the micros() function is tied to overflows and Timer0 counters, and calling it and manipulating a long integer takes a significant amount of time. Simple conditional-infinite loops in controllers, in principle, are not a very reliable thing.

The huge advantage of this method is that it allows you to use any digital output of the controller as a measuring one. The disadvantage is that the main loop freezes for the measurement time (for units of hertz, it takes crazy fractions of a second, by the standards of the controller) and at the same time it is not very desirable to use any other interrupts. Two other methods are practically devoid of this shortcoming, but they are tied to certain conclusions of the controller.

Method 2. Ideologically correct: use Timer1

In fact, this method should have been implemented in pure assembler, and not in the Arduino environment, then the maximum possible could be pulled out of it. But even so, given our frequency range of a few to tens of hertz, it will turn out well.

The way is that we run the 16-bit Timer1 in two modes at once: counting and capturing events. When counting, it is convenient to set a clock frequency divider of 1/8, then in a 16-MHz controller we will count the time in ticks of half a microsecond. On overflow (65536 half microseconds), an overflow interrupt is triggered, which increments the counter of the third bit (byte) of the long number. Three byte digits (16777216 half a microsecond or about 8 seconds) are quite enough for our purposes of calculating the frequency period of the order of units or tens of hertz. Upon capturing a level drop event, we fix the three-digit number of ticks that have passed since the previous such event (collecting it from the values ​​of the timer registers plus the third most significant bit), reset all variables and counting registers, and wait for the next drop. In theory, it would be necessary to clear the counters of the clock frequency prescaler, but they will still change when Timer0 is running (the prescaler for timers 0 and 1 is common), and with a divider of 1/8, this error will be insignificant.

Details for the curious: why this method is ideologically correct

This method is ideologically correct because everything happens within a single timer, which is not affected by anything else: the counting occurs mostly in hardware. In principle, some error here can only occur when the timer interrupt call is delayed due to coincidence with some other interrupt. But in the general case, this is such an improbable event that it can be ignored.

I guess there are a lot of people who would like to challenge this claim. There are a lot of sources on the Web that put forward the thesis that it is dangerous to use interruptions, because supposedly you can lose something. A deeply erroneous opinion: everything is exactly the opposite - just in the loop () loop, it is easy to lose, and very difficult in interrupts. A correct program should run predominantly on interrupts (with the exception of procedures that cannot be used there - like the sleep command). Only then can you squeeze the maximum possible out of the controller. Inside the controller, there are no functions that last so long as to significantly interfere with other functions in other interrupts, even if these are operations on long or float numbers. The longest of the operations - division of numbers of type long - is performed in about 670-680 cycles, that is, somewhere in 42 microseconds, and it rarely happens more than one for the entire interrupt. Here is an exchange with external environment takes much longer: for example, a 9600 byte transfer takes about a millisecond. But long exchange procedures with waiting for a response can be arranged in the program so as not to interfere with measuring or other time-critical operations. And if, nevertheless, your controller is clogged with lengthy computational procedures, then this means that the platform has been chosen incorrectly: go to 32 bits or even to the Raspberry Pi.

Let's take a closer look at this with our example. The counting of ticks in Timer1 is done in hardware and does not depend on anything. Overflow interrupts for our conditions (1/8 clock frequency 16 MHz) occur every 32 milliseconds. The interrupt itself consists of a single operation of incrementing a variable of the third digit with a size of one byte (see below). If I were to implement this in assembler, I would store the third bit in the working register and the operation would take exactly one cycle. In the case of Arduino (and implementations in C in general), the variable is stored in RAM, so a few cycles are still lost to retrieve / store in memory. Plus an interrupt call (7 cycles) and a return (4 cycles), that is, the entire duration of the procedure is on the order of a microsecond or a little more. From the duration of the interval between overflows (32 ms), this is approximately 0.003%. And what is the probability that some random event(e.g. pressing an external button) will occur at this exact moment? Even if you keep pressing the button as fast as you can, you will hardly be able to achieve a match.

But even if this incredible event occurs, the error will be at most the sum of the interrupt durations - ours and the one processing the interfering event. And if, as we said, at least for the duration of the measurement, one refrains from lengthy procedures such as dividing multibyte numbers or exchanging information through external ports, then when measuring sufficiently long periods, such an error will practically not affect the results.

What can really interfere with our interrupts is the periodic update of the millis() function through the Timer0 overflow interrupt, which occurs every millisecond and lasts a few microseconds (see this function in the wiring.c file). Regarding system time, our interrupts also occur in random moment, but the probability of stumbling upon a Timer0 interrupt is already on the order of a percent, which is quite a lot. If you want to go along with your perfectionism, then you should, strictly speaking, turn off Timer0 for the duration of the measurements. But if we take into account that the maximum error is a few microseconds, and we measure periods ranging from thousands to hundreds of thousands of microseconds, then this error can be ignored.

I will say right away: everything will look somewhat different when all these events are not random relative to each other. This will happen if the trigger edge is synchronized with the clock generator of the controller itself. It is for the purpose of testing what happens in this situation that the sketch has a commented out tone() function. The results of such a check are at the end of the article.


The sketch to test this idea looks like this:

#define Tone_PIN 12 // frequency output - see text #define IN_PIN 8 // frequency detection input /ICP1 - for memory volatile byte time2 = 0; //most significant bit of time, two least significant bits are timer registers volatile unsigned long ttime = 0; //Sensor response time volatile unsigned long time_old = 0; //Previous time volatile uint8_t flag=0; void setup() ( pinMode(IR_PIN, OUTPUT); //to output pinMode(IN_PIN, INPUT); //output frequency detection to input //set timer1 registers TCCR1A = 0; //Set edge capture and divider 1/8 to 16MHz clock TCCR1B =1<When displaying the results, we took into account that one tick of the timer is equal to 0.5 microseconds. In addition, a flag has been introduced here, which prevents the calculated value of the period from changing during the output. It will not affect the measurement process itself. The large delay at the end of setup is due to the need to wait some time, otherwise the first measurements will be erroneous. Its minimum value is equal to the period of the measured oscillations.

And where is the timeout for operation?

By the way, really, what will happen if there is no frequency at the input at all? This situation is not worked out in any way, because it is not required for the safe execution of the program. If you leave input 8 connected to some potential, then the value of the period (variable ttime) will simply not change - what was previously delayed in it. If it was some kind of frequency, it will be shown. If not a single pulse has slipped since the moment of loading, then there will be zero (in this case, the output limit). And if, by the way, we leave the input 8 hanging in the air, then the interference of 50 Hz will be measured with a fairly high accuracy.

Of course, if, according to the conditions of measurements, it is required to somehow work out the situation of the lack of frequency at the input, then you can enter a timeout to wait for the capture. For example, this can be done through the control in the main loop of the state of the same flag flag, the value of which will need to be changed in the capture interrupt. Now it will report that the interrupt happened (or didn't happen within the given time). And it will be advisable to use the measurement result when the measurement actually takes place, according to the state of the flag.


The measurement results for the same frequencies 2, 8, 64, as well as 2048 and 16384 hertz are combined in the table:

Note that interval measurements, even for high frequencies, give fairly stable results, if they differ from each other, then in the legal unit of the least significant digit. But for high frequencies, a systematic error of two extra microseconds is noticeable, which is why the frequency values ​​are overestimated. This is probably the influence of the simultaneously working Timer0, but for the frequencies we need in units of tens of hertz, the error will not matter. In addition, it is quite easy to take it into account in the calculations, if necessary, by calibrating the sketch using a reference frequency meter.

Method 3. The easiest: by external event

This is the easiest and most obvious way. We trigger an external interrupt (on the edge of the output) and at the same time fix the system time with the same micros () function. As soon as the second such interruption occurs, we calculate the difference and thus get the period.

There should be more sources of errors here, because the counter and external interrupts are systemically separated and a certain time passes before the readings are fixed. But, firstly, it can be expected that for long periods they will not matter, and secondly, in reality it turned out even better than expected.

The sketch that implements this idea looks like this:

#define Tone_PIN 12 // frequency output - see text #define IN_PIN 2 // frequency detection input volatile unsigned long ttime = 0; //Sensor response time volatile unsigned long time_old = 0; //Previous time volatile uint8_t flag=0; void setup() ( pinMode(IR_PIN, OUTPUT); //to output pinMode(IN_PIN, INPUT); //output frequency detection to input attachInterrupt(0, impuls, RISING); //Rising edge interrupt on D2 Serial.begin (9600); // tone(Tone_PIN, 8); delay(1000); ) void impuls()( if(flag!=1) ttime =micros()-time_old; time_old = micros(); ) void loop() ( flag=1; //so that ttime does not change during the output Serial.println(ttime); if (ttime!=0) (//in case there is no frequency float f = 1000000/float(ttime); // Calculate the frequency of the signal in Hz Serial.println(f,1);) flag=0;delay(500); )
The interrupt input here will be different - output number 2 (output PD2). As before, the flag prevents ttime from being changed during output. Regarding the lack of frequency at the input, the same considerations apply here as in the previous case. The results of measuring the same frequencies are presented in the table:


As we can see, here the measurement error is in full accordance with the resolution of the micros () function, equal to 4 μs - that is, in fact, the result fluctuates within plus or minus one tick of the system timer, everything is legal. This does not have the best effect on high frequency measurements, but it is quite suitable for our range. Therefore, this method can be recommended for common applications.

Measurement by the controller of the frequency generated by itself

In general, this is a purely cognitive task, probably not having practical applications. But it became interesting to me: what will happen if the measured frequency comes from the controller itself? It is convenient to use the tone() function for this, which takes Timer2, and therefore will not overlap with either the system time or Timer1 if it is used for measurement. For this purpose, this function is inserted in each of the sketches, which works through the Tone_PIN output.

For each of the sketches, the function was uncommented, and first it was checked whether the parallel operation of the tone() function affects measurements at different combinations of frequencies, both measured and generated. No clear effect was observed in any of the options. Then the Tone_PIN pin was connected directly to the IN_PIN frequency measurement input and the port monitor was started to monitor the results.

Actually, I expected to see one of two options as a result: a) measurements will work, as if nothing had happened; b) or, rather, the measurements will shamelessly lie, and with a regular systematic error (which should have been due to the addition of oscillations of two dependent timers). The reality turned out to be more interesting than assumptions: the measurements in all three cases worked normally, but in a limited range of specified frequencies. Moreover, the lower limit was determined exactly: the input period was measured correctly, starting from a frequency of 32 hertz. I did not determine the upper limit exactly - it is too troublesome, but approximately it is located slightly above 1000 Hz. Everything that is lower or higher is defined absolutely incorrectly. Moreover, what is most interesting, without any particular pattern: the readings in one series are the same, but after a reboot with the same set values, they become completely different.

I do not undertake to explain these results, but, probably, this is not very necessary. In practice, such a regime, as I have already said, is useless, and if necessary, it is enough to recall these empirical patterns.



If you find an error, please select a piece of text and press Ctrl+Enter.