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.  
    201. // ADC initialization
    202. // ADC disabled
    203. ADCSRA=0x00;
    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 :D 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 :D 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 :D

    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 :D

    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.
Loading...