Digital RPM indicator

IMAG0008

I made a circuit for RPM measurement based on an Atmel ATtiny24 to drive a Hitachi 44780 parallel interface character LCD. It uses an external interrupt request and INT0 to calculate the elapsed time between high/low transitions on the INT0 pin. The elapsed time is then converted to RPM and sent to the LCD. The code will work from RPM values up to 999, and a variable timeout is used for a low cutoff to display 0RPM. The RPM range could be extended to 9,999 or higher by modifying the decimal to BCD routine at the end of the program.

Sony 016

Here is the source code, it compiles with AVR Studio and AVR-GCC.
——————————————————————-

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#define F_CPU 	1000000;	// 1MHz

volatile unsigned long RPM=0;	// Global RPM variable

void initLCD(void);
void lcd_write_byte(unsigned int CONTROL, unsigned int DATA);
void updateLCD(unsigned long RPM_OLD);
void check_BF(void);

void main(void)
{
TCCR1B=(1<<CS10)|(1<<CS12);	// Set up prescaler for CLK/1024
GIMSK=(1<<INT0);		// Enable external interrupt INT0
MCUCR=(1<<ISC00)|(1<<ISC01);    // Setup INT0 for rising edge detection

unsigned long RPM_OLD=1;	// Internal loop RPM variable

DDRB=0x00;			// Set PB2 for input

initLCD();			// Initialize the LCD

sei();				// Enable interrupts

while(1)
{
if((RPM != RPM_OLD) & (TCNT1 < 4000))
{
RPM_OLD=RPM;		// If new updated RPM value is not the same
updateLCD(RPM_OLD);	// as the one inside the loop, update the LCD.
}

if(TCNT1 >= 4000)	// If it's been longer than 4 seconds, RPM=0.
{
RPM=0;
if (RPM != RPM_OLD)
{
RPM_OLD=RPM;		// If new updated RPM value is not the same
updateLCD(RPM_OLD);	// as the one inside the loop, update the LCD.
}
}
}
}

ISR(EXT_INT0_vect)	// External interrupt on PB2
{
unsigned long ELAPSED;

if(TCNT1 < 4000)	// If Timer1 value is valid:
{
ELAPSED=TCNT1;		// Get Timer1 value before it changes much
TCNT1=0;		// Reset Timer1
RPM=(58594/ELAPSED);	// Calculate RPM based on Timer1 elapsed time
}

if(TCNT1 >= 4000)	// If Timer1 overflowed, value is no good.
{
TCNT1=0;		// Reset Timer1
}
}

void initLCD()
{
DDRA=0xFF;			// Set PortA for output
_delay_ms(250);			// Wait for HD44780
PORTA=0x04;			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &= ~0x10;
_delay_ms(10);			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &= ~0x10;
_delay_ms(10);			// Intialize to 4 Bit, one line
PORTA |= 0x10;
_delay_ms(1);
PORTA &= ~0x10;
_delay_ms(10);
lcd_write_byte(0,0x0C);		// Turn on display
lcd_write_byte(0,0x06);		// Increment by one after write

}

void lcd_write_byte(unsigned int CONTROL, unsigned int DATA)
{
// check_BF();

if(CONTROL == 1) PORTA |= 0x20; else PORTA &= ~0x20;
if((DATA & 0x80) == 0x80) PORTA |= 0x01; else PORTA &= ~0x01;
if((DATA & 0x40) == 0x40) PORTA |= 0x02; else PORTA &= ~0x02;
if((DATA & 0x20) == 0x20) PORTA |= 0x04; else PORTA &= ~0x04;
if((DATA & 0x10) == 0x10) PORTA |= 0x08; else PORTA &= ~0x08;
PORTA |= 0x10;
_delay_us(1);
PORTA &= ~0x10;

if((DATA & 0x08) == 0x08) PORTA |= 0x01; else PORTA &= ~0x01;
if((DATA & 0x04) == 0x04) PORTA |= 0x02; else PORTA &= ~0x02;
if((DATA & 0x02) == 0x02) PORTA |= 0x04; else PORTA &= ~0x04;
if((DATA & 0x01) == 0x01) PORTA |= 0x08; else PORTA &= ~0x08;
PORTA |= 0x10;
_delay_us(1);
PORTA &= ~0x10;
_delay_ms(4);
}

void updateLCD(unsigned long RPM_OLD)
{
unsigned long RPM_BCD=((((RPM_OLD/10)+((RPM_OLD/100)*6))*16)+(RPM_OLD%10));

unsigned int ONES = 0x00;
unsigned int TENS = 0x00;
unsigned int HUND = 0x00;

if((RPM_BCD & 0x0800) == 0x0800) HUND |= 0x08; else HUND &= ~0x08;
if((RPM_BCD & 0x0400) == 0x0400) HUND |= 0x04; else HUND &= ~0x04;
if((RPM_BCD & 0x0200) == 0x0200) HUND |= 0x02; else HUND &= ~0x02;
if((RPM_BCD & 0x0100) == 0x0100) HUND |= 0x01; else HUND &= ~0x01;

if((RPM_BCD & 0x0080) == 0x0080) TENS |= 0x08; else TENS &= ~0x08;
if((RPM_BCD & 0x0040) == 0x0040) TENS |= 0x04; else TENS &= ~0x04;
if((RPM_BCD & 0x0020) == 0x0020) TENS |= 0x02; else TENS &= ~0x02;
if((RPM_BCD & 0x0010) == 0x0010) TENS |= 0x01; else TENS &= ~0x01;

if((RPM_BCD & 0x0008) == 0x0008) ONES |= 0x08; else ONES &= ~0x08;
if((RPM_BCD & 0x0004) == 0x0004) ONES |= 0x04; else ONES &= ~0x04;
if((RPM_BCD & 0x0002) == 0x0002) ONES |= 0x02; else ONES &= ~0x02;
if((RPM_BCD & 0x0001) == 0x0001) ONES |= 0x01; else ONES &= ~0x01;

ONES |= 0x30;
TENS |= 0x30;
HUND |= 0x30;

lcd_write_byte(0x00, 0x01);
lcd_write_byte(0x01, 0x20);
lcd_write_byte(0x01, HUND);
lcd_write_byte(0x01, TENS);
lcd_write_byte(0x01, ONES);
lcd_write_byte(0x01, 0x20);
lcd_write_byte(0x01, 0x52);
lcd_write_byte(0x01, 0x50);
lcd_write_byte(0x01, 0x4D);

}

void check_BF(void)
{
DDRA = 0xF0;  // Set PortA 4-7 (R/W, RS, E) to output, 0-3 (DBx) for input
PORTA &= ~0x20;			// Clear register select
PORTA |= 0x40;			// Set read
PORTA |= 0x10;			// Set enable
_delay_us(1);

while((PA0 & 0x01) == 0x01)	// Read DB7 of LCD (busy flag)
{
PORTA &= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
PORTA &= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
}

PORTA &= ~0x10;			// Clear enable
_delay_us(1);
PORTA |= 0x10;			// Set enable
_delay_us(1);
PORTA &= ~0x10;			// Clear enable
PORTA &= ~0x40;			// Clear read
DDRA = 0xFF;			// Set for output

}

Tags: , , , ,

Wednesday, August 26th, 2009 Electronics

7 Comments to Digital RPM indicator

  • Dave says:

    You might improve your code by reading about the timer1 input capture interrupt. You may have to use a different pin. The timer can be used free running (no need to reset to 0) and the elapsed time may be calculated by the difference, using the old stored in a “static” variable in the ISR. In the ISR also set a flag that a new Elapsed is available. In the main loop, use an “if” to check the flag, then do the division at that time, and clear the flag. By moving the division from the ISR improvement happens by minimizing the ISR code. This will help you in the future as you develop more complicated systems with many interrupts and calculations.

    It is also possible to do this with the external interrupt, and not have to change the pin. The advantage of the input capture is it stores the timer value for you in an ICR.

  • imsolidstate says:

    Thanks for your comment. I originally tried to use the ICP since this is what it is intended for, but I couldn’t get it to work. I can’t remember why. So I did it the less efficient way to get it done. I’m still learning a lot about programming, and most of my stuff is trial and error.
    I like the idea of using a flag and checking it from the main loop. When you say “flag” I assume you mean a global variable that you toggle. I’ll try that next time!

  • Dave says:

    Yes the flag is a global variable. I use CodeVision C compiler, it has bit variables. If your compiler does not support bits then a byte will do. Just set it to 1 for true, zero for false.

    Your mechanical work is very nice. You have great potential. Your programming is good too. You may ask me for programming help if you need it.

    I used a mega128 and built a working engine management system (fuel injection, ignition, idle speed and other controls).

    You may find info here: http://my.att.net/p/s/community.dll?ep=16&ext=1&groupid=372867&ck=

  • imsolidstate says:

    Thanks Dave! I’m suitably impressed with your engine management system. I remember building an interface and hooking up my laptop to the ALDL connector on my ’88 Blazer, I was totally overwhelmed by the complexity of all of the lookup tables and how it all worked together. I’m impressed you could build that yourself. Looks good!

  • Dave says:

    It has been a fun project. I was excited when it first worked. I did not sleep for a couple weeks, just to add code. You are correct, the bulk of the complexity is in all the setup information. The code that runs the engine is about 4K bytes (ISRs and calculations), the total code is about 20K bytes.

    I wish the best for you with your engineering degree. It is a excellent idea. With your present knowledge and interests you will a great student. You have an advantage by having interests and energy that will help steer your career.

  • RS Hamlett says:

    I’m wanting to build a 0 to 500 rpm counter for use on an 1894 steamboat.

    Your Digital RPM indicator looks like what I’m wanting! Question; is it possible to get a circuit diagram of same?

  • imsolidstate says:

    You can pretty much get the schematic by looking at the picture: Use one of the AVR’s ports to run the HD44780 controller in 4-bit mode, a connector for power, and a crystal if you aren’t using the onboard oscillator.

  • Leave a Reply