# Tach frequency calculation

Discussion in 'Embedded Systems and Microcontrollers' started by MONK3, Jun 3, 2014.

1. ### MONK3 Thread Starter New Member

May 25, 2014
4
0
Hi and hello to everyone!

I've read the threads regarding tachs and got a pretty good idea about their operating principles and have developed my own code for such a task, but I have encountered a problem (code will be at the end of the post). The software for writing the code is Codevision AVR and for the simulation I used Proteus.

I'm using an Atmega16's ICP functionality to capture the pulses from the engine's coil (well not yet anyway) using timer1. It's divided by a factor of 1024 so my timer's period is 128us. In the ICP's interrupt routine I pretty much save the value in ICR1L (disregarding the Higher 8bits and assuming the counter started from 0), calculate the time between the last pulse by multiplying ICR1L with T1_Period and then reversing it to obtain the frequency. I am also using a 4 digit 7 segment display to show my results, which seems to be working fine with other numbers, but not with the frequency. After a lot of tests and retries, I somehow noticed that my frequency computation result is always somewhat close to 1, so the best result should be 1, but it isn't always. I tried with several different frequencies, but ended up with no other solutions. I think the problem is the float variable used for holding the division answer, but I am not sure. It also won't let me use doubles.

Also, for a frequency of 5 hz, ICR's value was 195 in decimal, so , when computing the frequency by hand I get about 40 Hz. Am i thinking something wrong?

Thanks!

Code ( (Unknown Language)):
1.
2. [COLOR=#000][FONT=HelveticaNeue][COLOR=#000][FONT=HelveticaNeue]#include <mega16.h>
3.
4. #define MAX_UL 213
5. #define INTI 10
6.
7. #define DIG_1   PORTB.0 //OUTPUT    H1
8. #define DIG_2   PORTB.1 //OUTPUT    H2
9. #define DIG_3   PORTB.2 //OUTPUT    M1
10. #define DIG_4   PORTB.3 //OUTPUT    M2
11.
12. #define SEG_A   PORTA.0 //OUTPUT    L1
13. #define SEG_B   PORTA.1 //OUTPUT    L2
14. #define SEG_C   PORTA.2  //OUTPUT    L3
15. #define SEG_D   PORTA.3 //OUTPUT
16. #define SEG_E   PORTA.4 //OUTPUT
17. #define SEG_F   PORTA.5 //OUTPUT
18. #define SEG_G   PORTA.6 //OUTPUT
19. #define SEG_DP  PORTA.7 //OUTPUT
20.
21. #define pulse   PORTC.0 //OUTPUT
22.
23. #define var1   PORTB.6 //OUTPUT
24. #define var2   PORTB.7 //OUTPUT
25.
26. #define F_OSC   8000000
27. #define F_T1    7813
28. //#define T_T1    1/F_T1
29. //#define T_T1    0.000128
30. #define T_T1    128
31.
32. int  counter=0;
33. unsigned char new_val = 0;
34. unsigned char old_val = 0;
35. unsigned char dif = 0;
36. unsigned char dif1 = 0;
37. float time = 0;
38. float freq = 0;
39. float tix = T_T1;
40.
41. int x = 0;
42. static unsigned char dig = 0;
43. static unsigned char seg_1 = 0;
44. static unsigned char seg_2 = 0;
45. static unsigned char seg_3 = 0;
46. static unsigned char seg_4 = 0;
47.
48. void selectDigit(unsigned char digit);
49. void displayDigit(unsigned char number);
50. void display7SEG_ch(unsigned char h1, unsigned char h2, unsigned char m1, unsigned char m2);
51. void my_delay(int rep);
52. void pulse_f();
53.
54. // Timer1 input capture interrupt service routine
55. interrupt [TIM1_CAPT] void timer1_capt_isr(void)
56. {
57.     counter++;
58.     pulse_f();
59.
60.     //dif = TCNT1L;
61.     dif1 = ICR1L;
62.     dif = TCNT1L;
63.     //dif = new_val - old_val;
64.     time = dif * T_T1;
65.     freq = 1 / time;
66.     TCNT1L = 0x00;
67.     TCNT1H = 0x00;
68.     ICR1L = 0x00;
69.     ICR1H = 0x00;
70. }
71.
72. // Timer 0 output compare interrupt service routine
73. interrupt [TIM0_COMP] void timer0_comp_isr(void)
74. {
75.     x++;
76.     if (x == INTI)    // == 10 => 100ms
77.     {
78.         dig++;
79.         if (dig == 5)
80.         {
81.             dig = 1;
82.         }
83.         selectDigit(dig);
84.
85.         switch (dig)
86.         {
87.         case 1:
88.         displayDigit(seg_1);
89.         break;
90.
91.         case 2:
92.         displayDigit(seg_2);
93.         break;
94.
95.         case 3:
96.         displayDigit(seg_3);
97.         break;
98.
99.         case 4:
100.         displayDigit(seg_4);
101.         break;
102.         };
103.          x=0;
104.     }
105. }
106.
107. // Declare your global variables here
108.
109. void main(void)
110. {
111. // Declare your local variables here
112.
113. // Input/Output Ports initialization
114. // Port A initialization
115. // Func7=Out Func6=Out Func5=Out Func4=Out Func3=Out Func2=Out Func1=Out Func0=Out
116. // State7=0 State6=0 State5=0 State4=0 State3=0 State2=0 State1=0 State0=0
117. PORTA=0x00;
118. DDRA=0xFF;
119.
120. // Port B initialization
121. // Func7=Out Func6=Out Func5=Out Func4=Out Func3=Out Func2=Out Func1=Out Func0=Out
122. // State7=0 State6=0 State5=0 State4=0 State3=0 State2=0 State1=0 State0=0
123. PORTB=0x00;
124. DDRB=0xFF;
125.
126. // Port C initialization
127. // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In Func1=In Func0=In
128. // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
129. PORTC=0x00;
130. DDRC=0x01;
131.
132. // Port D initialization
133. // Func7=In Func6=In Func5=In Func4=In Func3=In Func2=In  Func1=In Func0=In
134. // State7=T State6=T State5=T State4=T State3=T State2=T State1=T State0=T
135. PORTD=0x00;
136. DDRD=0x00;
137.
138. // Timer/Counter 0 initialization
139. // Clock source: System Clock
140. // Clock value: 125,000 kHz
141. // Mode: CTC top=OCR0
142. // OC0 output: Disconnected
143. TCCR0=0x0B;
144. TCNT0=0x00;
145. OCR0=0x26;
146.
147. // Timer/Counter 1 initialization
148. // Clock source: System Clock
149. // Clock value: 7,813 kHz
150. //  Mode: Normal top=0xFFFF
151. // OC1A output: Discon.
152. // OC1B output: Discon.
153. // Noise Canceler: Off
154. // Input Capture on Rising Edge
155. // Timer1 Overflow Interrupt: Off
156. // Input Capture Interrupt: On
157. // Compare A Match Interrupt: Off
158. // Compare B Match Interrupt: Off
159. TCCR1A=0x00;
160. //TCCR1B=0xC5;
161. TCCR1B=0x45;
162. TCNT1H=0x00;
163. TCNT1L=0x00;
164. ICR1H=0x00;
165. ICR1L=0x00;
166. OCR1AH=0x00;
167. OCR1AL=0x00;
168. OCR1BH=0x00;
169. OCR1BL=0x00;
170.
171. // Timer/Counter 2 initialization
172. // Clock source: System Clock
173. // Clock value: Timer2 Stopped
174. // Mode: Normal top=0xFF
175. // OC2 output: Disconnected
176. ASSR=0x00;
177. TCCR2=0x00;
178. TCNT2=0x00;
179. OCR2=0x00;
180.
181. // External Interrupt(s) initialization
182. // INT0: Off
183. // INT1: Off
184. // INT2: Off
185. MCUCR=0x00;
186. MCUCSR=0x00;
187.
188. // Timer(s)/Counter(s) Interrupt(s) initialization
189. TIMSK=0x22;
190.
191. // USART initialization
192. // USART disabled
193. UCSRB=0x00;
194.
195. // Analog Comparator initialization
196. // Analog Comparator: Off
197. // Analog Comparator Input Capture by Timer/Counter 1: Off
198. ACSR=0x80;
199. SFIOR=0x00;
200.
204.
205. // SPI  initialization
206. // SPI disabled
207. SPCR=0x00;
208.
209. // TWI initialization
210. // TWI disabled
211. TWCR=0x00;
212.
213. // Global enable interrupts
214. #asm("sei")
215.
216. while (1)
217.       {
218.       // Place  your code here
219.       float y = 0;
220.       int i=0,m,s,z,d;
221.
222.         for (i=0;i<10000;i++)
223.         {
224.             //i = counter;
225.             //i = TCNT1L //-> works
226.             //i = freq;
227.             m = i / 1000;
228.             s = (i / 100) % 10;
229.             z = (i / 10) % 10;
230.             d = i % 10;
231.             display7SEG_ch(m,s,z,d);
232.             my_delay(100);
233.             if(freq < -1 && freq > -2)
234.                 {var1 = 1;
235.                  var2 = 0;}
236.             else
237.                 {var1 = 0;
238.                  var2 = 1;}
239.         }
240.         //display7SEG_ch(1,2,3,4);
241.       }
242. }
243.
244. void my_delay(int rep)
245. {
246.     int a;
247.     unsigned long i;
248.
249.     for (a=0;a<rep;a++)
250.     {    for  (i=0;i<MAX_UL;i++)
251.         {
252.
253.         }//3
254.     }//rep
255. }//my_delay
256.
257. void pulse_f() // debug purposes
258. {
259.     int i=1;
260.
261.     pulse = 1;
262.     while(i--){};
263.     i=1;
264.     pulse = 0;
265.     while(i--){};
266. }[/FONT][/COLOR][/FONT][/COLOR]
267.

2. ### MONK3 Thread Starter New Member

May 25, 2014
4
0
I removed the functions regarding the 7seg display in order to fit the text in my previous post, I doubt they are important, but can post them at request

3. ### THE_RB AAC Fanatic!

Feb 11, 2008
5,435
1,305
I think you need to post the specs before we can really nut-out timer period calcs.

What is the minimum and maximum RPM value (or Hz) that you expect from the ignition coil?

I don't work with Atmega16 but with a PIC I would use the capture/compare hardware module to automatically grab the period between two tach pulses.

Then the next step would be to display that capture value, to confirm the capture and timers are all working ok.

Once you have confirmed the period capture is working and giving the correct period, the math in C code to convert that period to RPS or RPM is quite straightforward.

MONK3 likes this.
4. ### MONK3 Thread Starter New Member

May 25, 2014
4
0
Oh, i forgot about that The engine is a four-stroke four-cylinder gas engine, and after some digging I found that for every 30 pulses I get an RPM. That, or another option was that the sparks come at half the RPM Don't know yet, I need to experiment with that. The maximum would be 5500RPM. Thus, my freq range should be between 0 and 200 Hz max (25-175 actually).

Sorry, my knowledge in PIC uCs is pretty useless, but I didn't figure in the Atmega an option to automatically give the difference between pulses, so I compute it by resetting the timer at every RETI.

I still think that the issue is somewhere concerning variables and copmutation, but any idea is an idea

5. ### THE_RB AAC Fanatic!

Feb 11, 2008
5,435
1,305
That would be unusual... Normally a 4 cylinder engine with one coil and a distributor makes two sparks per crankshaft rev.

Maybe you meant 1Hz = 30 RPM? That sounds more like it, or 2Hz = 60 RPM, because there are two pulses per rev.

Resetting the timer introduces errors. It is better to let the timer free-run, and just grab its value, then compute the timer period between two grabbed values. That way there is no accumulated error from delays reading or resetting the timer.

MONK3 likes this.
6. ### MONK3 Thread Starter New Member

May 25, 2014
4
0
This makes sense to me, but I haven't taken this into consideration due to the previous problem Let me take this again, 2 sparks/rev makes my "input frequency" from 0->5500 to 0->5500/30(least) and by multiplying the result by 2 I can find the number of rotations /minute? I dunno why this is giving me such a headache

This is how I thought of it:
From the coil, using your suggestion, there are X pulses. From that, the number of revolutions per second is X/2, therefore by transforming this to RPM we multiply it by 60 and the result is 30*X. So, by employing this algorithm, I need to sample the number of pulses from the coil during a second, then multiply them by 30 and get the RPM, right? And if the sampling period is 1/10th of a second, this also needs to multiply my result by 10, right? Sorry for the idiocracy of my comment

That was the first idea, but I could only get the values from the input capture register in Hi & Lo 8 bit variables, so concatenating them seemed to be a more cumbersome operation for me and the uC in order for proper results.

Thank you for your responses !

7. ### THE_RB AAC Fanatic!

Feb 11, 2008
5,435
1,305
OK, so it's a standard 4 cyl 4 stroke engine with distributor, giving 2 pulses per rev. We're slowly getting there.

Revs are not 0-5500, the engine idle speed will be maybe 500 RPM at the lowest, so the RPM is 500-5500. That might sound like nitpicking but it is important in choosing the software method used to measure the period because now we have a defined minimum and maximum period.

I get 600 RPM = 10 RPS = 20Hz. (ratio RPM:Hz = 600:20 = 30:1)

So you did well, if you multiply Hz *30 you get the figure in RPM.

Of course, you are not measuring Hz (freq) at all, you're measuring period. And your real calc should convert period to RPM;
X / period = RPM
(where you need to decide the value of constant "X")

So at 600 RPM, ie 20Hz pulses, if your period is in uS the period = 50000uS
so constant X = 30mil;
30000000 / 50000 = 600 RPM

Please be aware that the forum does not allow discussion of automotive modifications on road vehicles for safety reasons. If the moderators think this is for a vehicle they will shut down the thread. Up to this point I have assumed this might be for a generator or something, but you should clarify that.

MONK3 likes this.