Car dial tachometer for a beginner or a bit of fixed-point shamanism on the AVR
Hello! I would like to share with the community my story about the modernization of the TX-193 tachometer.
A week ago, one person turned to me with a rather non-standard task - it was necessary to ensure the work of the ancient TX-193 tachometer (VAZ 2106) with amodern VAZ21126 engine (Priora), which has an individual ignition system coils for each cylinder, which means that simply connecting the TX-193 to the ignition coil will no longer work. In addition, the customer wanted to improve the operational qualities of the device, leaving its appearance and design untouched. In general, the case ended up with me taking off the electronic stuffing of the device and developing my own, with blackjack and whores. The tachometer will now receive information on the engine speed from the ECU on January 7.2, for which the latter has a special conclusion.
Under the cut photo, video, diagram, source, and a lot of text that tells about the logarithms and how to properly scale the data and get rid of the comma.
Hard
Let's start with the TX-193 . The mechanical part of the device is a milliammeter of classical design, with a permanent magnet and a movable coil, which drives the arrow.
To develop the circuit, in fact, it was enough to know about the milliammeter only that at a current of the order of 10 mA, the arrow deviates to the limit, and the resistance of the winding is approximately 180 Ohms. The ATtiny2313A controller of the renowned Atmel company, clocked from an external quartz resonator at 16 MHz, was chosen as the brain. The device is powered from the vehicle’s on-board network, which means that according to GOST it must withstand a “beard” of up to 100V and work stably in the range from 9-15V. Due to low consumption (several tens of milliamps), it was decided to use a 7805 linear stabilizer with an inductive filter and a suppressor to protect against impulse noise. The device was assembled from what was at hand, therefore, in the finished product a powerful version of 7805 is used, although 78L05 at 100mA would be quite enough.
The controller controls the milliammeter, naturally, using PWM. What was involved 16-bit timer in Phase and Frequency Correct PWM mode.
Information on the rotational speed of the crankshaft is transmitted from the computer in the form of pulses from 0 - 12V. Active level is low. 2 pulses per 1 revolution of the crankshaft. To capture these pulses, the external INT0 interrupt and the corresponding chain of the RC filter, suspenders and protective diodes are used. In general, the circuitry of the device is quite typical and I was surprised to find that I just wrote so much about it. But don’t judge strictly, the first article is all the same.
The assembled device without a dial now looks like this:
Software
In fact, even before plotting the circuit, I quickly assembled the whole thing on a breadboard, taking the controller in the DIP package and immediately started waving the arrow))
In general, the software turned out to be a little more interesting than hard.
Let's start with the general architecture:
Timer 0 is ticking at a frequency of 250 kHz, which means that the tick period = 4 μs, the overflow interrupt occurs at a frequency of 250 kHz / 256 = 0.976 kHz,
which means that the interrupt occurs once every 1024 μs. It was possible to get confused and fit this thing closer to one millisecond by updating the timer counter in the interrupt, but in this task there is nothing to it. Those. we can measure time with an accuracy of 4 µs, which is quite enough for a given accuracy of the device.
Timer 0 not only counts the time, but also sets flags to run certain tasks with a certain frequency.
We have two tasks. Give the signal to the INT0 interrupt to measure the pulse period at the input and change the position of the arrow.
Timer 1 is ticking at a frequency of 16 MHz, but because it is 16-bit and uses the Phase and Frequency Correct PWM mode - the resulting PWM frequency is very small and amounts to about 122 Hz. This is because the timer ticks first up and then down. But we have a true 16-bit PWM and we can very accurately steer the arrow! The datasheet contains all the details.
The mechanics, by the way, turned out to be of disgusting quality, it was not realistic to move the arrow smoothly due to the increased friction in the mechanism, which had to be lubricated with gear oil at first. But these are the details.
A table of correspondence of the device readings with the corresponding value of the timer register in the PWM parrots was compiled.
In the source, this case is called GAUGE_TABLE and is taken out of habit in a separate file.
It was further discovered that if just in one fell swoop to change the current in the ammeter circuit in order to, for example, move the arrow 1000 forwards, then it will make two, three or four oscillations in the region of the target mark, which was completely unacceptable and that the customer paid Attention. The fact is that these tachometers initially had such a problem, and several times having thundered in time with the oscillations, you can make the needle swing with a significant amplitude (more than half the scale!).
There was something to do with this. My idea was to bring the arrow to the mark with a series of smaller steps, gradually approaching the goal. Actually, this part is the most interesting and useful for beginners, as requires some skill. After all, dealing with a microcontroller, calling log2 () in a loop is, to put it mildly, not the most successful idea. In addition, the 8-bit architecture imposes even more restrictions. Well, about the "floating" (floating point), and you need to forget at all. But all these difficulties, as always, lead only to a deeper understanding of the processes and calculations made by the processor.
For some reason, it turns out more and more text, but I just can’t stop in more detail at this point!
So it’s clear that we need a logarithmic progression. The step of changing the current in the milliammeter circuit should decrease as it approaches the target mark. Resources are worth its weight in gold, which means only a tabular method. Points are also possible at least.
Let's start by building a logarithmic table.
Everything is very simple: we run excel and with a few sweeps of the mouse we get 50 values of the base 2 logarithm for a sequence from 1 to 50. For clarity, we build a beautiful graph.
Perfectly! Exactly what is needed! But firstly, there are 50 points, and secondly, all floating-point numbers. This does not suit us in any way!
Therefore, we select 5 points from the existing array in steps of 10. We get something like this:
Already better. A consistent approach to the goal is still preserved, but there are 10 times less points.
Next, you need to normalize the resulting set. Those. make sure that all values are in the range from 0 to 1. To do this, simply divide each element into 5.64385618977472 (the maximum value of our array).
Thus, we get the same logarithmic dependence, but in a much more convenient form for further calculations. Such a table can already be quite easily applied, if not for the point after zero. But with this we are also pretty easy to deal with.
Now I want us to take the beautiful value of 1024 per unit and recalculate our table again. We
get As you can see, the shape of the graph has not changed, but the numbers now fit into the 16-bit range and there are no fractions.
In the source code, the resulting array is called logtable []. The
scaling factor (if you can call it that) 1024 did not appear here by chance and you need to understand very well why exactly 1024.
Firstly, it is a power of two and it was chosen because expensive division and multiplication by power deuces can be replaced by a cheap left / right shift and it would be foolish not to use this opportunity.
Secondly, the coefficient should be selected based on the scale of the data to which it will be applied. In our case, these are the values of the register of a 16-bit timer that controls the filling of the PWM. It was experimentally found that unsatisfactory fluctuations of the arrow are detected even with its sharp shift by 200 rpm. Those. if you need to move the arrow by more than ~ 200 rpm - smoothing will be required. The GAUGE_TABLE table shows that neighboring cells on average differ by 4000 PWM of parrots, which corresponds to approximately 500 rpm on the scale of the device. It is not difficult to estimate that in figures the arrow shift by 200ob will be 4000 / 2.5 = 1600 PWM of parrots.
Therefore, the scaling factor must be chosen so that, firstly, it is as large as possible, because otherwise we lose bits and accuracy, and secondly, as small as possible, so as not to force us to switch from 16-bit variables to 32-bit ones and not spend resources in vain. As a result, we choose the smallest power of two, which is less than 1600 and provides sufficient accuracy. This will be 1024.
This moment is very important. I myself still sometimes experience difficulties in choosing the right coefficients and sizes of variables.
Well, then it went, went. We find the implementation of display_rpm () in the code and see that the GAUGE_TABLE [] table is used to determine the specific value in the PWM parrots and the assumption that the scale is linear between adjacent marks. To organize the change of current according to the logarithmic law, an array of 5 points pwm_cuve [] is introduced which contains a set of values that must be sequentially subtracted or added (depending on the direction of arrow movement) from pwm_ocr1a_cur_val to make the arrow move smoothly and clearly.
each step is formed by multiplying the pwm_delta value by a coefficient from our logtable [] table;
Before multiplication, the value is pre-scaled by dividing by 1024.
The final calculated destination of the arrow target_pwm is written in pwm_cuve [] as it is, because due to problems with rounding and due to the limitation of the dimension of variables to 16 bits, the exact value as a result of calculations will not be formed there very often, so you have to ensure that the arrow will end its path at a given point.
In general, all of the above is essentially enclosed in a single line.
Next, the main cycle, by a signal from the timer, pwm_cuve 0 times in PWM_UPD_PERIOD and assigns them to the variable pwm_ocr1a_cur_val, the value of which in the interrupt will be assigned to the OCR1A register, which will immediately lead to a change in the PWM filling and a change in current in a milliammeter circuit.
Here, in fact, almost all the tricks, except for the translation of the period presented in the ticks of the timer to the crankshaft rotation frequency, which is measured in rpm.
All this has been reduced to
How this figure turned out, we can talk or not talk next time, because without that the text turned out quite a lot and obviously not many read even to this place.
Honestly, a few more “tricks” were used in the code that may not seem obvious to beginners. If someone wants to understand in more detail - welkam in kamenty and hp.
A little video, as promised.
Do not pay attention to the accuracy of the readings, the arrow is not properly dressed + the dial is not twisted.
Arrow movement in increments of 1000 rpm in one jump.
Smooth change of current
The thing is clear that in reality there will be no jumps at 1000 rpm, and those minor flights of the arrow that can still be observed on the video will not become a problem. Just if you eliminate them too, then you can coolly lose the device’s speed and its readings will lag behind reality.
PS Not to say that the archive is completely govnokod, but yes, in some places it could be made more beautiful. Yes, I know that magic numbers are bad and yes, I could do better. On the other hand, getting lost in the source of 200 lines is quite difficult, so in some places I allowed myself a little bit of hacking.
I just wanted to log in to the hub for a long time, and to write any kind of detailed article after the lapse of time after the implementation of the project is becoming more difficult, so I decided that today they would be "leading from the fields."
So the real code from a real device assembled for a real period of 7 evenings, which will be installed tomorrow on a glorious VAZ 2108 car with an engine 21126 and I hope it will please the owner for a long time, who agreed to put as much as 100 evergreens for my work.
But we all know that I have done all this way, not only and not so much for the sake of money. It's so nice when you created something and it even works!
Thearchived Atmel studio project and circuit + board in Altium designer. The board was made by the LUT method.
UPD:The archive was posted on a free file hosting service and therefore died suddenly. To store the archive on habrastorage, I built it into the tachometer photo without a dial (it is at the top of the article). In general, jpg you need to save yourself and open the winrar. You can also just change the extension to zip.
UPD2: The circuit and the board have been redesigned, the pictures have been updated, the archive is still in the picture.
UPD3 Archive is no longer inserted into pictures. Write to the PM here or find me vk.com/trotskyi
See you soon!
Checking the device on a car
The customer is very satisfied!
And when I saw this article and all the sources, including some photos of the board manufacturing process itself, he said that his brain was blown up!
A week ago, one person turned to me with a rather non-standard task - it was necessary to ensure the work of the ancient TX-193 tachometer (VAZ 2106) with a
Under the cut photo, video, diagram, source, and a lot of text that tells about the logarithms and how to properly scale the data and get rid of the comma.
Hard
Let's start with the TX-193 . The mechanical part of the device is a milliammeter of classical design, with a permanent magnet and a movable coil, which drives the arrow.
To develop the circuit, in fact, it was enough to know about the milliammeter only that at a current of the order of 10 mA, the arrow deviates to the limit, and the resistance of the winding is approximately 180 Ohms. The ATtiny2313A controller of the renowned Atmel company, clocked from an external quartz resonator at 16 MHz, was chosen as the brain. The device is powered from the vehicle’s on-board network, which means that according to GOST it must withstand a “beard” of up to 100V and work stably in the range from 9-15V. Due to low consumption (several tens of milliamps), it was decided to use a 7805 linear stabilizer with an inductive filter and a suppressor to protect against impulse noise. The device was assembled from what was at hand, therefore, in the finished product a powerful version of 7805 is used, although 78L05 at 100mA would be quite enough.
The controller controls the milliammeter, naturally, using PWM. What was involved 16-bit timer in Phase and Frequency Correct PWM mode.
Information on the rotational speed of the crankshaft is transmitted from the computer in the form of pulses from 0 - 12V. Active level is low. 2 pulses per 1 revolution of the crankshaft. To capture these pulses, the external INT0 interrupt and the corresponding chain of the RC filter, suspenders and protective diodes are used. In general, the circuitry of the device is quite typical and I was surprised to find that I just wrote so much about it. But don’t judge strictly, the first article is all the same.
The assembled device without a dial now looks like this:
Software
In fact, even before plotting the circuit, I quickly assembled the whole thing on a breadboard, taking the controller in the DIP package and immediately started waving the arrow))
In general, the software turned out to be a little more interesting than hard.
Let's start with the general architecture:
Timer 0 is ticking at a frequency of 250 kHz, which means that the tick period = 4 μs, the overflow interrupt occurs at a frequency of 250 kHz / 256 = 0.976 kHz,
which means that the interrupt occurs once every 1024 μs. It was possible to get confused and fit this thing closer to one millisecond by updating the timer counter in the interrupt, but in this task there is nothing to it. Those. we can measure time with an accuracy of 4 µs, which is quite enough for a given accuracy of the device.
Timer 0 not only counts the time, but also sets flags to run certain tasks with a certain frequency.
We have two tasks. Give the signal to the INT0 interrupt to measure the pulse period at the input and change the position of the arrow.
Timer 1 is ticking at a frequency of 16 MHz, but because it is 16-bit and uses the Phase and Frequency Correct PWM mode - the resulting PWM frequency is very small and amounts to about 122 Hz. This is because the timer ticks first up and then down. But we have a true 16-bit PWM and we can very accurately steer the arrow! The datasheet contains all the details.
The mechanics, by the way, turned out to be of disgusting quality, it was not realistic to move the arrow smoothly due to the increased friction in the mechanism, which had to be lubricated with gear oil at first. But these are the details.
A table of correspondence of the device readings with the corresponding value of the timer register in the PWM parrots was compiled.
In the source, this case is called GAUGE_TABLE and is taken out of habit in a separate file.
It was further discovered that if just in one fell swoop to change the current in the ammeter circuit in order to, for example, move the arrow 1000 forwards, then it will make two, three or four oscillations in the region of the target mark, which was completely unacceptable and that the customer paid Attention. The fact is that these tachometers initially had such a problem, and several times having thundered in time with the oscillations, you can make the needle swing with a significant amplitude (more than half the scale!).
There was something to do with this. My idea was to bring the arrow to the mark with a series of smaller steps, gradually approaching the goal. Actually, this part is the most interesting and useful for beginners, as requires some skill. After all, dealing with a microcontroller, calling log2 () in a loop is, to put it mildly, not the most successful idea. In addition, the 8-bit architecture imposes even more restrictions. Well, about the "floating" (floating point), and you need to forget at all. But all these difficulties, as always, lead only to a deeper understanding of the processes and calculations made by the processor.
For some reason, it turns out more and more text, but I just can’t stop in more detail at this point!
So it’s clear that we need a logarithmic progression. The step of changing the current in the milliammeter circuit should decrease as it approaches the target mark. Resources are worth its weight in gold, which means only a tabular method. Points are also possible at least.
Let's start by building a logarithmic table.
Everything is very simple: we run excel and with a few sweeps of the mouse we get 50 values of the base 2 logarithm for a sequence from 1 to 50. For clarity, we build a beautiful graph.
Perfectly! Exactly what is needed! But firstly, there are 50 points, and secondly, all floating-point numbers. This does not suit us in any way!
Therefore, we select 5 points from the existing array in steps of 10. We get something like this:
Already better. A consistent approach to the goal is still preserved, but there are 10 times less points.
Next, you need to normalize the resulting set. Those. make sure that all values are in the range from 0 to 1. To do this, simply divide each element into 5.64385618977472 (the maximum value of our array).
Thus, we get the same logarithmic dependence, but in a much more convenient form for further calculations. Such a table can already be quite easily applied, if not for the point after zero. But with this we are also pretty easy to deal with.
Now I want us to take the beautiful value of 1024 per unit and recalculate our table again. We
get As you can see, the shape of the graph has not changed, but the numbers now fit into the 16-bit range and there are no fractions.
In the source code, the resulting array is called logtable []. The
scaling factor (if you can call it that) 1024 did not appear here by chance and you need to understand very well why exactly 1024.
Firstly, it is a power of two and it was chosen because expensive division and multiplication by power deuces can be replaced by a cheap left / right shift and it would be foolish not to use this opportunity.
Secondly, the coefficient should be selected based on the scale of the data to which it will be applied. In our case, these are the values of the register of a 16-bit timer that controls the filling of the PWM. It was experimentally found that unsatisfactory fluctuations of the arrow are detected even with its sharp shift by 200 rpm. Those. if you need to move the arrow by more than ~ 200 rpm - smoothing will be required. The GAUGE_TABLE table shows that neighboring cells on average differ by 4000 PWM of parrots, which corresponds to approximately 500 rpm on the scale of the device. It is not difficult to estimate that in figures the arrow shift by 200ob will be 4000 / 2.5 = 1600 PWM of parrots.
Therefore, the scaling factor must be chosen so that, firstly, it is as large as possible, because otherwise we lose bits and accuracy, and secondly, as small as possible, so as not to force us to switch from 16-bit variables to 32-bit ones and not spend resources in vain. As a result, we choose the smallest power of two, which is less than 1600 and provides sufficient accuracy. This will be 1024.
This moment is very important. I myself still sometimes experience difficulties in choosing the right coefficients and sizes of variables.
Well, then it went, went. We find the implementation of display_rpm () in the code and see that the GAUGE_TABLE [] table is used to determine the specific value in the PWM parrots and the assumption that the scale is linear between adjacent marks. To organize the change of current according to the logarithmic law, an array of 5 points pwm_cuve [] is introduced which contains a set of values that must be sequentially subtracted or added (depending on the direction of arrow movement) from pwm_ocr1a_cur_val to make the arrow move smoothly and clearly.
each step is formed by multiplying the pwm_delta value by a coefficient from our logtable [] table;
Before multiplication, the value is pre-scaled by dividing by 1024.
The final calculated destination of the arrow target_pwm is written in pwm_cuve [] as it is, because due to problems with rounding and due to the limitation of the dimension of variables to 16 bits, the exact value as a result of calculations will not be formed there very often, so you have to ensure that the arrow will end its path at a given point.
In general, all of the above is essentially enclosed in a single line.
pwm_cuve[ table_i ] = pwm_ocr1a_cur_val + (pwm_delta / LOG_TABLE_MAX * logtable[ table_i ]);
Next, the main cycle, by a signal from the timer, pwm_cuve 0 times in PWM_UPD_PERIOD and assigns them to the variable pwm_ocr1a_cur_val, the value of which in the interrupt will be assigned to the OCR1A register, which will immediately lead to a change in the PWM filling and a change in current in a milliammeter circuit.
Here, in fact, almost all the tricks, except for the translation of the period presented in the ticks of the timer to the crankshaft rotation frequency, which is measured in rpm.
All this has been reduced to
engine_rpm = (uint16_t)(15000000UL / (uint32_t)rot_time);
How this figure turned out, we can talk or not talk next time, because without that the text turned out quite a lot and obviously not many read even to this place.
Honestly, a few more “tricks” were used in the code that may not seem obvious to beginners. If someone wants to understand in more detail - welkam in kamenty and hp.
A little video, as promised.
Do not pay attention to the accuracy of the readings, the arrow is not properly dressed + the dial is not twisted.
Arrow movement in increments of 1000 rpm in one jump.
Smooth change of current
The thing is clear that in reality there will be no jumps at 1000 rpm, and those minor flights of the arrow that can still be observed on the video will not become a problem. Just if you eliminate them too, then you can coolly lose the device’s speed and its readings will lag behind reality.
PS Not to say that the archive is completely govnokod, but yes, in some places it could be made more beautiful. Yes, I know that magic numbers are bad and yes, I could do better. On the other hand, getting lost in the source of 200 lines is quite difficult, so in some places I allowed myself a little bit of hacking.
I just wanted to log in to the hub for a long time, and to write any kind of detailed article after the lapse of time after the implementation of the project is becoming more difficult, so I decided that today they would be "leading from the fields."
So the real code from a real device assembled for a real period of 7 evenings, which will be installed tomorrow on a glorious VAZ 2108 car with an engine 21126 and I hope it will please the owner for a long time, who agreed to put as much as 100 evergreens for my work.
But we all know that I have done all this way, not only and not so much for the sake of money. It's so nice when you created something and it even works!
The
UPD:The archive was posted on a free file hosting service and therefore died suddenly. To store the archive on habrastorage, I built it into the tachometer photo without a dial (it is at the top of the article). In general, jpg you need to save yourself and open the winrar. You can also just change the extension to zip.
UPD2: The circuit and the board have been redesigned, the pictures have been updated, the archive is still in the picture.
UPD3 Archive is no longer inserted into pictures. Write to the PM here or find me vk.com/trotskyi
See you soon!
Checking the device on a car
The customer is very satisfied!
And when I saw this article and all the sources, including some photos of the board manufacturing process itself, he said that his brain was blown up!