NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » Digital Engine Tach

January 19, 2011
by huzbum
huzbum's Avatar

as a baby step to my CDI project I'm making an ignition tachometer. I haven't wired up the hardware yet, but the input is a pulse from a wire coiled around one of the spark plug wires into a 2n3055 transistor then into a mosfet. Tests with a volt meter showed 20-50 volts.

It is necessary for my intentions to calculate RPM each revolution on the fly rather than count revolutions over a timed interval.

After hours reading and re-reading timer and capture tutorials and the Atmega168 datasheet I put this code together:

// tach.c
// for NerdKits with ATmega168
// nhusby@gmail.com

// PIN DEFINITIONS:
//
// ICP1 - (pin 14) Ignition input capture

#define F_CPU 14745600
#define ICP PINB0

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

volatile uint16_t revTick;      // Ticks per revolution
volatile uint8_t  revCtr;   // revolution counter
volatile uint16_t RPM;      // Revolutions per minute

void setupTimer() 
{           // setup timer
   TCCR1A = 0;      // normal mode
   TCCR1B = 4;      // (00000100) Timer = CPU Clock/256 -or- 14745600/256 -or- 57.6KHz 
   TCCR1C = 0;      // normal mode
   TIMSK1 = 32;     // (00010000) Input capture interupt enabled 
}

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   if (ICP) //if rising edge
   {
      revTick = ICR1L + (ICR1H<<8);     // combine high and low 8 bit registers, save elapsed revolution time
      ICR1H = 0;            // restart timer for next revolution
      ICR1L = 0;            // High bit must be written before Low bit
      revCtr++;             // add 1 to engine revolution count
   }
}

int main (void)
{
   setupTimer();    
   lcd_init();      
   FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

   while (1)
   {  // do calculations and talk to lcd while we're not doing anything...

      if (revTick > 0)              // You're not Chuck Norris, DO NOT DIVIDE BY ZERO.
      {
         RPM = 3456000 / revTick;   // 3456000 ticks/minute

         if (revCtr > RPM/120)      // Every 1/2 second if things work right...
         {
            lcd_home();     
            lcd_write_string(PSTR("     RPM: "));   
            lcd_write_int16(RPM);   
            lcd_line_two();     
            fprintf_P(&lcd_stream, PSTR("Ticks/REV: %.0f"), revTick);   
            lcd_write_data(0xdf);

            revCtr = 0;
         }
      }
      else
      {
         lcd_home();    
         lcd_write_string(PSTR("   Start Engine... "));  
         lcd_write_data(0xdf); 
      }
   }

   return 0;
}

Then it occurred to me... will this interfere with the USB download interface? Is there any way around this? I want to keep as much done in hardware as possible, because I intend to add more demanding functions, so if I can use the timer1 capture function I would certainly prefer to, but not if it means I can't program the chip again.

Also, do you see any other problems?

January 20, 2011
by Ralphxyz
Ralphxyz's Avatar

What is your concern with interference with USB download.

I was having problems with using PB0 (ICP1) munging the bootloader but now I am able to use the bootloader fine overwriting the loaded program as expected.

I sure cannot wait to digest your code, I have been on a similar quest for the past three or four weeks scouring the web for examples of RPM counting.

I have just yesterday finally found a tutorial that I could understand and could modify to meet my needs and now you post your code.

Thank you so much.

Ralph

January 20, 2011
by huzbum
huzbum's Avatar

The above code compiles successfully, but I have not yet downloaded it to my MCU for fear of it disabling the bootloader. If I program pin14 to do something else, will it still work for enabling the programming mode? I do not have another way to program the chip if it will not work with the usb programming cable. So it's not that I have experienced problems with it, but that I wish to prevent that... rather safe than sorry...

I seem to recall reading somewhere that using ICP1 caused problems with the USB cable, but maybe that was just an issue with live communication, which would not bother me.

I am glad to see that my code will be of use to more than just me. It's untested and I'm still just learning micro-controllers, so be careful what you take from it. If you are looking for a more reliable source, I put my code together based on what I learned from the temp sensor project, these tutorials: http://www.protostack.com/blog/2010/09/external-interrupts-on-an-atmega168/ http://www.protostack.com/blog/2010/09/timer-interrupts-on-an-atmega168/ and the atmega 168 datasheet.

As I mentioned my future applications for this code require an on the fly estimation of current engine speed. As soon as I have this code tested and working I will modify it to accept a timing pulse and then produce the ignition pulse, so I can't wait for more than one revolution to make my estimate. If your applications do not share that requirement, then it would be much simpler to read average RPM data over a given time. You could simply have a pin change interrupt add to a counter and read, reset, and output to LCD at timed intervals with a timer interrupt.

January 20, 2011
by Ralphxyz
Ralphxyz's Avatar

I compiled your code, and get the stuck two black programming bars in run mode!

This is either OK or a conflict with the bootloader.

I had assumed it was a conflict with the bootloader so had switched to using a programmer (ATmel Dragon) and flashing the .hex directly without using the bootloader. Now I realize that this also could be because of lack of output going to the LCD with the code compiling and loading correctly.

The two black bars are also possible because I am not generating a pulse, I have modified tempsensor code that outputs a pulse with the width dependent on position of a potentiometer taking the place of the LM34. I will put your code and my rpm simulator code together to see if we can get this to work!

re: "You could simply have a pin change interrupt add to a counter and read, reset, and output to LCD at timed intervals with a timer interrupt."

Simple in principle but I have been struggling with that for three to four weeks :-)

Your code compiles fine!

Here are the changes I made to the tempsensor code to generate a variable length pulse using a pot on PIN23 in place of the LM34.

/*
 PIN DEFINITIONS:
 PC0 -- Pot 0 - 5 volts input that varies a voltage on PIN23 PC0
 PB4 -- 50ms Pulsed output Pin18
 PB0 -- ICP1 Counter/Timer1 Input Capture Unit Pin14  
//*/

//* PIN ON (high) for simulation of revolutions 
    DDRB |= (1<<PB4);    //Data Direction Output  // LED as output
        {
        PORTB |= (1<<PB4);      // turn on LED
        delay_ms(50);           // ON for 50 milliseconds 
        PORTB &= ~(1<<PB4);     // turn off LED
        delay_ms(last_sample);  // OFF from pot setting (lower pot faster flash)    
        }

//*

This produces a variable ADC: reading! Using the tempsensor project code.

Jump a wire from PB4(PIN18) to PB0(PIN14).

Adjust the pot to vary the time of the pulse.

Ralph

January 20, 2011
by huzbum
huzbum's Avatar

hmm... after reviewing my code I see that if the counter has started before a pulse is detected it will not produce output for the LCD. I also noticed that I hadn't changed the print code for line2 to accept an integer instead of a double. I believe I fixed these problems... probably a better way of doing it, but this should work.

// tach.c
// for NerdKits with ATmega168
// nhusby@gmail.com

// PIN DEFINITIONS:
//
// ICP1 - (pin 14) Ignition input capture

#define F_CPU 14745600
#define ICP PINB0

#include <stdio.h>
#include <math.h>

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>

#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

volatile uint16_t revTick;      // Ticks per revolution
volatile uint8_t  revCtr;   // revolution counter
volatile uint16_t RPM;      // Revolutions per minute

void setupTimer() 
{           // setup timer
   TCCR1A = 0;      // normal mode
   TCCR1B = 4;      // (00000100) Timer = CPU Clock/256 -or- 14745600/256 -or- 57.6KHz 
   TCCR1C = 0;      // normal mode
   TIMSK1 = 32;     // (00010000) Input capture interupt enabled 
}

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   if (ICP) //if rising edge
   {
      revTick = ICR1L + (ICR1H<<8);     // combine high and low 8 bit registers, save elapsed revolution time
      ICR1H = 0;            // restart timer for next revolution
      ICR1L = 0;            // High bit must be written before Low bit
      revCtr++;             // add 1 to engine revolution count
   }
}

int main (void)
{
   setupTimer();    
   lcd_init();      
   FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

   uint16_t dispRPM = 0;    
   uint16_t dispRevTick = 0;

   while (1)
   {  // do calculations and talk to lcd while we're not doing anything...

      if (revTick > 0) // You're not Chuck Norris, DO NOT DIVIDE BY ZERO.
      {
         RPM = 3456000 / revTick;   // 3456000 ticks/minute

         if (revCtr > RPM/120)      // Every 1/2 second if things work right...
         {
            dispRPM = RPM;
            dispRevTick = revTick;
            revCtr = 0;
         }
      }

      lcd_home();   
      lcd_write_string(PSTR("     RPM: "));     
      lcd_write_int16(dispRPM);     
      lcd_line_two();   
      lcd_write_string(PSTR("Ticks/REV: "));    
      lcd_write_int16(dispRevTick); 
      lcd_write_data(0xdf);     
   }

   return 0;
}

Ralph, have you tried to re-program the MCU after having flashed this program to it? If so, I would be comfortable trying it, otherwise I'll have to wait till I get a programmer.

January 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Yes I reprogramed but as I could not get any output I do not know if it was effective. The compiler did not complain.

I will try your new code.

Ralph

January 20, 2011
by Ralphxyz
Ralphxyz's Avatar

Well this is progress!

I combined your latest code with my RPMsimulator code (modified temp sensor code).

I compiled on the Nerdkit using the command line then copied the .hex over to my pc with the Dragon and flashed the .hex to the mcu deleting the bootloader.

I see

        RPM: 0
Ticks/REV:  0

I have a flashing led on PB4 which is jumped to PB0 (ICP1).

Your code is not registering any flashes.

Also I am not able to vary the led flash rate so I missed something in porting my code (and probable also yours) so I will look further into this tomorrow.

Also I will compare with my code that uses PB0 that I am able to load using the bootloader, it has to be doable.

So at least now I am able to get output to the LCD.

My code is uploaded to my web site (work in progress).

You can load the .hex but chances are you will only see the two black bars. I have to load the .hex using the Dragon.

Possible you can see something in simulator.c I have not gone over it yet.

Ralph

January 21, 2011
by huzbum
huzbum's Avatar

I don't know if the problem is in your pulse generator, my pulse detector code, or the display update code.

You can try removing the if statement in the display update code.

if (revCtr > RPM/120)      // Every 1/2 second if things work right...

Maybe add some debug data to the output to see what's happening...

lcd_line_three();   
lcd_write_string(PSTR("revCtr: "));    
lcd_write_int16(revCtr); 
lcd_write_string(PSTR("   sample: "));    
lcd_write_int16(last_sample);

The other thing to note is that when an interrupt hits, anything in the main code is interrupted and put on hold while the interrupt code is executed. Not sure what affect that would have on delay_ms()

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Well this was not easy and took a lot of hair pulling and beard stroking.

You cannot use ICP1(PB0) as signal capture pin IF you are using the BootLoader!!

This is opposite with what I would think but if you put a pulse onto PB0(PIN14) the bootloader grabs it as a signal to switch to programming mode, hence you (I) get the two black bars!! Or possible PB4 is pulled to ground at startup if it is wired to PB0.

Now this is only at bootup!!

If I remove the signal wire (jumper from PB4) start the program and after the program starts insert the jumper once the program is running the program continues running. So there might be a way to do this if you can keep PB0 floating at the start. Now "normally" once deployed there will not be wire between PB4 and PB0 so possible we will not face this situation. It is a very intriguing situation (at least for me it is).

If I load the hex using my Dragon (flash with no bootloader) the signal jumper can stay inserted and the program runs.

So the boot loader seeing a non floating PB0 thinks programming mode is desired.

The programming switch goes to ground for programming and "floats" for run mode.

But also if PB0 is pulled high it goes to programming mode. I do not know if it just the pulse or if you connected PB0 to constant Vcc it would goto programming mode at startup.

All of my processors stopped "running" and it was driving me crazy trying to figure out why, until I realized it was the jumper wire.

Now I can start to look to see why your program is not recognizing a pulse. The pulse last 50ms might that be to fast?

I can easily slow the pulse down. The frequency of the pulse which also stopped working last night is variable from real slow to pretty fast. Either fast or slow the duration of the pulse is 50ms.

So with this caveat you can use the NerdKit bootloader to program and re-program your mcu during development but for deployment you will need to come up with a way to float PB0 during start up.

Again here is my current project

I was composing this while huzbum replied to my previous post so I'll post a reply that next.

Whew,

Ralph

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Here is a problem (I think) PB0 is at Vcc all of the time.

This is with the boot loader installed I'll try with out the boot loader.

Ralph

January 21, 2011
by huzbum
huzbum's Avatar

Too much trouble... I'll make a software work-around for a different pin... A few clocks aren't going to make a big difference with the clock/256 pre-scaler. If I need the clock cycles later I'll have to get a programmer and run without the bootloader.

Should have new code soon.

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Without the bootloader I see approx. 205 mv which I guess is the float value.

So I guess I will have to work with the Dragon programmer.

Even with 205mv I still get no RPM.

Speaking of programmers, I really like the ATmel Dragon programmer it cost $49.00.

The only other programmers I have been exposed to were a ISP (Inline Serial Programmer) which the developers considered beta and never released (which by the way worked great once I got the AVRDUDE syntax figured out).

I also have a ATmel STK600.

I like using ATmel devices instead of the cheaper knockoffs from ebay as when ATmel makes changes or adds devices the Dragon is updated so any future device "should" be supported.

Of course the Dragon has it's issues. There is no Dragon lair, it is literally a bare PCB board with a 40 pin zif socket. You have to solder the access pins on yourself and makeup your own cabling. Because of no box the earlier version crapped out if you touched them while energized in certain places.

So if you have a soldering iron I recommend the Dragon.

Ralph

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

I have been wanting to use ICP1(PB0) because of the Input Capturing feature:

15.2.1

    The Input Capture Register can capture the Timer/Counter value at a given external (edge triggered)
    event on either the Input Capture pin (ICP1) or on the Analog Comparator pins (See
    ”Analog Comparator” on page 244) The Input Capture unit includes a digital filtering unit (Noise
    Canceler) for reducing the chance of capturing noise spikes.

The digital filtering means you should not have to use additional debounce code.

Of course noise depends on what you use to generate your pulse so the filtering might not be needed.

Keep me posted with your new code.

Ralph

January 21, 2011
by huzbum
huzbum's Avatar

heh... silly thing... I never ENABLED interrupts! LoL We're missing an "sei()" in the main code!

int main (void)
{
   sei();  
   setupTimer();    
   lcd_init();      
   FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

   uint16_t dispRPM = 0;    
   uint16_t dispRevTick = 0;

LoL I removed it from the source I was working with because I was going to put it somewhere specific (who knows what I was thinking?) and I forgot all about it...

Hope this fixes it...

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Darn, I was hoping that would be it but no.

That was certainly necessary but I am still getting zero rpm.

I am going to connect a momentary contact switch to PB0 to by pass my code (which isn't working any longer).

Ralph

January 21, 2011
by Ralphxyz
Ralphxyz's Avatar

Nope, same results using a switch.

Ralph

January 23, 2011
by huzbum
huzbum's Avatar

OK. Fixed innumerable errors in my code. There was no way that code would have worked. Had bits wrong, writing to wrong registers, etc. I have much more confidence in this code, though I'm too tired to double check it. I eliminated a lot of opportunities for mistakes.

I think I'm going to have to order a programmer... until then, I may rewrite my code to use pin change interrupts in place of the ICP1, but input capture seems to me the best way to do this.

// tach.c
// for NerdKits with ATmega168
// nhusby@gmail.com

#define F_CPU 14745600
#define ICP PINB0

// PIN DEFINITIONS:
//
// ICP1 - (pin 14) Ignition pulse input capture

#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

volatile uint16_t revTick;      // Ticks per revolution

void setupTimer() 
{           // setup timer
   TCCR1A = 0;      // normal mode
   TCCR1B = 68;     // (01000100) Rising edge trigger, Timer = CPU Clock/256 -or- 14745600/256 -or- 57.6KHz 
   TCCR1C = 0;      // normal mode
   TIMSK1 = 17;     // (00010001) Input capture and overflow interupts enabled

   TCNT1 = 0;       // start from 0
}

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   revTick = ICR1;      // save duration of last revolution
   TCNT1 = 0;       // restart timer for next revolution
}

ISR(TIMER1_OVF_vect)    // counter overflow/timeout
   { revTick = 0; }     // RPM = 0

int main (void)
{
   sei();       // enable global interrupts.
   setupTimer();    // set timer perameters
   lcd_init();      
   FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

   uint16_t RPM;    // Revolutions per minute

   while (1)
   {  // do calculations and talk to lcd while we're not doing anything...

      if (revTick > 0) // You're not Chuck Norris, DO NOT DIVIDE BY ZERO.
         {RPM = 3456000 / revTick;}     // 3456000 ticks/minute  
      else 
         {RPM = 0;}

      lcd_home();   
      lcd_write_string(PSTR("     RPM: "));     
      lcd_write_int16(RPM);     
      lcd_line_two();   
      lcd_write_string(PSTR("Ticks/REV: "));    
      lcd_write_int16(revTick); 
      lcd_write_data(0xdf);

      delay_ms(499);    // wait 1/2 a second to update... I can't read that fast...

   }

   return 0;
}
January 24, 2011
by Ralphxyz
Ralphxyz's Avatar

Nope, your code compiles fine but I am not getting anything for RPM: or Ticks/REV:.

I am not even getting a flicker so it looks like RPM and revTick are not being incremented.

Maybe you could ask in the Support thread (What is wrong with this code) and get opinion.

I am working on my own code so I have not looked at your code at all beyond loading it.

Maybe I'll take a look.

I am using my pulse code and also just fliping a switch (Vcc) to get a pulse.

Ralph

January 24, 2011
by Ralphxyz
Ralphxyz's Avatar

huzbum, I added a count variable to your code but it is not getting incremented!

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   revTick = ICR1;      // save duration of last revolution
   TCNT1 = 0;       // restart timer for next revolution
   count++;
}

...

 lcd_home(); 
      lcd_home();   
        lcd_write_string(PSTR("ADC: "));
        lcd_write_int16(last_sample);
        lcd_write_string(PSTR(" of 1024   ")); 
      lcd_line_two();
      lcd_write_string(PSTR("     RPM: "));     
      lcd_write_int16(RPM);     
      lcd_line_three();   
      lcd_write_string(PSTR("Ticks/REV: "));    
      lcd_write_int16(revTick); 
      lcd_write_data(0xdf);
      lcd_line_four();
      lcd_write_string(PSTR("COUNT: "));
      lcd_write_int16(count);

Nothing is being touched.

Ralph

January 24, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Huzbum and Rick,

I think the reason your interrupts are not firing is due to an error in the setup routine. Up on line 28 of Huzbums code you meant to set the ICIE1 bit of the TIMSK1 register. This means you want to set bit 5, and your current code is setting bit 4 (remember the bit numbers are zero indexed). I would suggest changing that line to set the registers the way we usually do:

TIMSK1 = (1<<ICIE1) | (1<<TOIE1);     // (00100001) Input capture and overflow interrupt enabled

This is more readable, and slightly better from a code design standpoint.

Another point I would like to make here is that using a pin change interrupt would probably work just fine in this case. You would have to think about the denouncing yourself, but you would trade away the added complexity of using the same pin as the programming switch.

Let me know if that does the trick.

Humberto

January 24, 2011
by Ralphxyz
Ralphxyz's Avatar

Thanks Humberto, I made your change but still getting the same results.

Ralph

January 24, 2011
by Ralphxyz
Ralphxyz's Avatar

I have made further changes and I get the "count" variable I added to increment with button push and from the pulse from my code pulsing PB4!!

I have a button on PB0 to Vcc (+).

When I push the button I increment count. But not RPM or revTick!!

Here is the change to the latest program:

void setupTimer() 
{  // setup timer
   TCCR1A = 0;      // normal mode
   //TCCR1B = 68;     // (01000100) Rising edge trigger, Timer = CPU Clock/256 -or- 14745600/256 -or- 57.6KHz 
   TCCR1B|=(1<<ICES1);  ////First capture on rising edge
   TCCR1C = 0;      // normal mode
   //TIMSK1 = 17;     // (00010001) Input capture and overflow interupts enabled
    TIMSK1 = (1<<ICIE1) | (1<<TOIE1); // (00100001) Input capture and overflow interrupt enabled
   TCNT1 = 0;       // start from 0
}

This with my prior code change for the LCD.

Here that code is again:

 lcd_home();    
  lcd_home();   
    lcd_write_string(PSTR("ADC: "));
    lcd_write_int16(last_sample);
    lcd_write_string(PSTR(" of 1024   ")); 
  lcd_line_two();
  lcd_write_string(PSTR("     RPM: "));     
  lcd_write_int16(RPM);     
  lcd_line_three();   
  lcd_write_string(PSTR("Ticks/REV: "));    
  lcd_write_int16(revTick); 
  lcd_write_data(0xdf);
  lcd_line_four();
  lcd_write_string(PSTR("COUNT: "));
  lcd_write_int16(count);

So I have the TIMER1_CAPT_vect interrupt capturing the count!!

Here is that code again:

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   revTick = ICR1;      // save duration of last revolution
   TCNT1 = 0;       // restart timer for next revolution
   count++;
}

Now I am looking at RPM and revTick but at least we know the interupt is working!

Ralph

January 25, 2011
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Ralph,

It looks like you are no longer setting any of the clock bits on the TCCR1B register. huzbum's code was setting the clock prescaler to 256, you don't necessarily have to do the same thing, but you need to set one of the bits to something to turn the Timer/Counter module on. See the chart on page 134 of the datasheet.

Humberto

January 25, 2011
by Ralphxyz
Ralphxyz's Avatar

I was primarily just trying to get some acknowledgement that the ISR was being called.

Thanks, now that I know the pulse is being detected I'll re-implement huzbum's code.

Ralph

January 27, 2011
by huzbum
huzbum's Avatar

Sorry for my slow update, but I finally have working code. ICP1 works fine without interfering with the boot-loader or programming function. I have PC4 delivering a 120RPM (2hz) test pulse jumpered directly to ICP1.

I'm still working on my high voltage signal pickup. I attempted to hook in to the tach signal on the distributor of my 89 f-150 and it was bad news... DO NOT rustle around in 20 year old wiring unless you intend to replace it. Spent all day getting fuel and spark back.

So, here's my working code for all to use:

// tach.c
// for NerdKits with ATmega168
// nhusby@gmail.com

#define F_CPU 14745600
#define ICP PINB0

// PIN DEFINITIONS:
//
// ICP1 - (pin 14) Ignition pulse input capture

#include <stdio.h>
#include <math.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"

volatile uint16_t revTick;      // Ticks per revolution
volatile uint16_t revCtr;

void setupTimer() 
{           // setup timer
   TCCR1A = 0;      // normal mode
   TCCR1B = 68;     // (01000100) Rising edge trigger, Timer = CPU Clock/256 -or- 14745600/256 -or- 57.6KHz 
   TCCR1C = 0;      // normal mode
   TIMSK1 = 33;     // (00100001) Input capture and overflow interupts enabled

   TCNT1 = 0;       // start from 0
}

ISR(TIMER1_CAPT_vect)  // PULSE DETECTED!  (interrupt automatically triggered, not called by main program)
{
   revTick = ICR1;      // save duration of last revolution
   TCNT1 = 0;       // restart timer for next revolution
   revCtr++;
}

ISR(TIMER1_OVF_vect)    // counter overflow/timeout
   { revTick = 0; }     // RPM = 0

int main (void)
{
   sei();       // enable global interrupts.
   setupTimer();    // set timer perameters
   lcd_init();      
   FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

   PORTB|=(1<<ICP);     //pullup enabled
   DDRB&=~(1<<ICP);     //ICR1 as input
   DDRC |= (1<<PC4);    // PC4 as output

   uint16_t RPM;    // Revolutions per minute

   while (1)
   {  // do calculations and talk to lcd while we're not doing anything...

      if (revTick > 0) // You're not Chuck Norris, DO NOT DIVIDE BY ZERO.
         {RPM = 3456000 / revTick;}     // 3456000 ticks/minute  
      else 
         {RPM = 0;}

      lcd_home();   
      lcd_write_string(PSTR("     RPM: "));     
      lcd_write_int16(RPM);     
      lcd_line_two();   
      lcd_write_string(PSTR("Ticks/REV: "));    
      lcd_write_int16(revTick);     
      lcd_line_three();
      lcd_write_string(PSTR("revolutions: "));      
      lcd_write_int16(revCtr);

      PORTC |= (1<<PC4);    // test signal  120RPM
      delay_ms(10);         
      PORTC &= ~(1<<PC4);

      delay_ms(425);    // wait 1/2 a second to update... I can't read that fast...
   }

   return 0;
}
January 28, 2011
by Ralphxyz
Ralphxyz's Avatar

huzbum, thanks so much for your code, it's funny how so many people will ask questions and post code they are having problems with but never publish there working code.

Your code is working!!

I have a jumper between PC4 and PB0.

I let your code run for a couple of hours and now I cannot get to program mode!

Your program can not be modified and recompiled!

I have not looked at your code yet so I do not know what to expect.

I am seeing RPM: 10 Ticks/REV: -31351(2) switches between 1 and 2 revolution: increments 1 per second (apprx)

Ralph

January 29, 2011
by huzbum
huzbum's Avatar

That is odd... I've never experience any of those problems, but I've also never left it run longer than like 5 or 10 minutes or so...

Sounds like you're having signal problems. Maybe you should try a resistor or diode in place of the jumper wire. also, maybe your battery is going dead, so a fresh battery may help.

The test pulse is based on the time it takes to do all of the operations plus the delay_ms() time.

Also, do you have the boot-loader installed, or just the program?

January 29, 2011
by huzbum
huzbum's Avatar

There does seem to be some degree of error for the first second or two... possibly as it is booting up? But after about a second and a half mine holds steady at 120 RPM and 28768 ticks/revolution. incrementing one revolution at a time. If you're getting more than one revolution then your pulse is being counted twice!

I would certainly try a new battery and if that doesn't do it, a resistor and/or diode.

January 29, 2011
by Ralphxyz
Ralphxyz's Avatar

I use a pda power supply not a battery (i'd go broke).

I am using the bootloader to do the initial load.

But I cannot reload (program) the mcu first I have to re-install the bootloader.

Are you able to re-load the program?

This is much more convenient to use the bootloader, what did you add to the code (I know I could look, which I will) to get it to work?.

I do not believe you need line 79 "delay_ms(425);" I can read the display results fine without the delay.

I'll put the delay back in just so we are looking at the same code. Possible the behavior is different without the delay.

Ralph

January 30, 2011
by huzbum
huzbum's Avatar

the time it takes to run through the program is what sets the frequency of PC4. Without the delay you'll be running a much higher and more random frequency.

The test frequency is just a quick addition of turning pc4 on for 10ms each time it goes through the loop.

May 26, 2011
by huzbum
huzbum's Avatar

Now that it's not 20 below I finally got around to testing this on a vehicle. I soldered a hall effect switch to about 5ft of twisted pair wire and set it next to a spark plug. It works great! There are only 2 issues.

1) if you run the wire parallel to the spark plug wire, it will induce voltage in your wire and make funky things happen... so try to keep your wires away from the plug wires.

2) depending on your engine, you may need to multiply your RPM x 2. The 4 stroke engine only needs spark once every other revolution. Engines with a distributor will need to multiply the RPM x 2. Engines with a waste spark ignition (coil pack, etc) will not need to double the RPM count.

Also, not sure if I mentioned this earlier, but the hall effect switch does not interfere with the boot loader or programmer. I have my kit set up in a cigar box and I just leave everything connected and coiled up in the box. If I want to program I just flip the program switch and reload it.

Post a Reply

Please log in to post a reply.

Did you know that you can make a huge, multi-panel LED display? Learn more...