Full Version : Easy Optical RPM (CVC)
avr >>LIGHT & VISION PROJECTS >>Easy Optical RPM (CVC)


AVR_Admin- 04-16-2006
Easy RPM

The RPM counter is a pretty funny thing. It uses an optical sensor to measure the RPM. However the sensor that I uses is not the fork type but the type that sends out IR light and then the object that you measure reflects the light back into the sensor.

This way its easy to measure on almost everything. Your drilling machine, the fan in your computer or like me the RPM on my remote controlled helicopters head rotor (while actually hovering above ground).

With this kind of sensor you don't have to place the sensor into the moving object, it's enough to point the light beam onto the target.

Nice example of some good techniques

This project was a very helpful example for an interupt, extending a 16-bit timer to 32 bits, basic AVR circuit, scanning across multiple 7-seg LEDs with one set of outputs, using an IR sensor.

The C source is commented very well, it is easy to understand.

CODE
/*********************************************
Project : RPM counter
Version : 1.0
Date    : 19-08-2004
Author  : Jan Thogersen                  
Email   : jan@future-design.dk
Company :                                
Comments: The project here measures the time
         between two pulses and calculates
         the RPM from a average of several
         measurements.
         The project uses a 16 bit timer to
         measure the time between pulses,
         however, to extend the RPM range
         the 16 bit hardware timer is extended
         with an extra software layer. The end
         product is a 32 bit timer. Which makes
         the measurement very accurate and with
         a large range. To minimize the software
         footprint the timer is running in
         Indput Capture mode. So the essential
         of this project is done 100% in hardware.

Chip type           : AT90S2313
Clock frequency     : 8,000000 MHz
Memory model        : Tiny
Internal SRAM size  : 128
External SRAM size  : 0
Data Stack size     : 32
*********************************************/

#include <90s2313.h>


// Here is the definition of which port pin is used for which LED in the matrix.

// The following defines is the ROW in the matrix.
// These values can be changed to fit the PCB layout.
#define A    4
#define B  128  
#define C   32
#define D    2
#define E    1
#define F   16
#define G   64
#define Dod    8

// The following defines is the COL in the matrix.
// These values can be changed to fit the PCB layout.
#define COL_A   0x1C
#define COL_B   0x2C
#define COL_C   0x34
#define COL_D   0x38

// Definition of the 7 segment numbers
//         A
//      -----    
//   F | G  | B
//    |----|
// E |    | C
//   -----o
//     D   Dod

const unsigned char kucDigi[] = {
   (A+B+C+D+E+F),   // 0
   (B+C),   // 1
   (A+B+G+E+D),   // 2
   (A+B+C+D+G),   // 3
   (B+C+F+G),   // 4
   (A+C+D+F+G),   // 5
   (A+C+D+E+F+G),   // 6
   (A+B+C),   // 7
   (A+B+C+D+E+F+G),   // 8
   (A+B+C+D+F+G),   // 9
   (0)    // blank
};

const unsigned char kucCOLUMS[] = {COL_A, COL_B, COL_C, COL_D};

#define  NumMeasures  2  // The output is a average of the X readings  

unsigned char ucLEDS[4], ucLED_POS;
unsigned char ucCiffer[4];
unsigned int  uiTempDigi, uiOutput;
unsigned long ulMeasuredTime;
unsigned char ucOutputOutOfRange;
unsigned long ulMeasured[NumMeasures];
unsigned char ucMeasuredPos;

// Timer 0 overflow interrupt service routine
interrupt [TIM0_OVF] void timer0_ovf_isr(void)
{
// Place your code here
   // Here is the 7 segment matrix scanner.
   TCNT0=0xF0; // This value can be changes to scan faster or slower.
   PORTD = kucCOLUMS[ucLED_POS];
   PORTB = ucLEDS[ucLED_POS];
   ucLED_POS++;
   if (ucLED_POS == 4) ucLED_POS = 0;
}

// Timer 1 overflow interrupt service routine
interrupt [TIM1_OVF] void timer1_ovf_isr(void)
{
// Place your code here

   // When we receive a Timer overflood interrupt
   // then we add 1 to the high 16 bit of the
   // ulMeasuredTime. Why?
   // The timer is 16 bit. Thats not enough so we add
   // another 16 bit, however this time in software.
   // The end product is a 32 bit unsigned long ulMeasuredTime.
   // The low 16 bit is for the hardware value from the timer, the
   // high 16 bit is for the software timer part.
   // So a the overflood we simply add 1 to the software part.
   // Later when we have the Input Capture Interrupt we can complete
   // the process by adding the hardware value from the timer
   // to the low 16 bit of the ulMeasuredTime.

   ulMeasuredTime += 0x10000;  

   // The ucOutputOutOfRange is a variable used to
   // verify that we have an input or not. It's a form
   // of timeout. When there is a overflood like now, then
   // we decrease the value by one.
   // If the value reaches zero, then we make the
   // assumption that there is no signal.
   if (ucOutputOutOfRange > 0) ucOutputOutOfRange--;
}

// Timer 1 input capture interrupt service routine
interrupt [TIM1_CAPT] void timer1_capt_isr(void)
{
// Place your code here
   if (ucOutputOutOfRange < 16) ucOutputOutOfRange+=4;

   // Place the hardware timer value into the 32 bit accumulation.
   ulMeasuredTime += ICR1;
   TCNT1 = 0;

   // The output is based on an average, so we save the measurements.
   // This is not the best way to do it. I know that. But it was fast to code
   // And I had enough RAM to do it. But a waste I know.
   ulMeasured[ucMeasuredPos] = ulMeasuredTime;
   ucMeasuredPos++;
   if (ucMeasuredPos >= NumMeasures) ucMeasuredPos = 0;

   ulMeasuredTime = 0;
}

// Declare your global variables here
char cTempCnt;
unsigned long ulSum;

void main(void)
{
// Declare your local variables here

// Input/Output Ports initialization
// Port B initialization
// Func0=Out Func1=Out Func2=Out Func3=Out Func4=Out Func5=Out Func6=Out Func7=Out
// State0=1 State1=1 State2=1 State3=1 State4=1 State5=1 State6=1 State7=1
   PORTB=0xFF;
   DDRB=0xFF;

// Port D initialization
// Func0=Out Func1=Out Func2=Out Func3=Out Func4=In Func5=In Func6=In
// State0=1 State1=1 State2=1 State3=1 State4=T State5=T State6=T
   PORTD=0x3C;
   DDRD=0x3C;

// Timer/Counter 0 initialization
// Clock source: System Clock
// Clock value: 31,250 kHz
   TCCR0=0x04;
   TCNT0=0x00;

// Timer/Counter 1 initialization
// Clock source: System Clock
// Clock value: 125,000 kHz
// Mode: Normal top=FFFFh
// OC1 output: Discon.
// Noise Canceler: On
// Input Capture on Faling Edge
   TCCR1A=0x00;
   TCCR1B=0x83;
   TCNT1H=0x00;
   TCNT1L=0x00;
   OCR1H=0x00;
   OCR1L=0x00;

// External Interrupt(s) initialization
// INT0: Off
// INT1: Off
   GIMSK=0x00;
   MCUCR=0x00;

// Timer(s)/Counter(s) Interrupt(s) initialization
   TIMSK=0x8A;

// Analog Comparator initialization
// Analog Comparator: Off
// Analog Comparator Input Capture by Timer/Counter 1: Off
// Analog Comparator Output: Off
   ACSR=0x80;

// Global enable interrupts
#asm("sei")

   uiOutput =  0;

   while (1) {

       ulSum = 0;

       // Calculate the average
       for (cTempCnt = 0; cTempCnt < NumMeasures; cTempCnt++) ulSum += ulMeasured[cTempCnt];
       uiOutput = (long)ulSum / NumMeasures;

       // Calculate the RPM from the average time between pulses.
       uiTempDigi = (long)(125000*60*NumMeasures) / ulSum;

       // Place your code here
       if (ucOutputOutOfRange > 4) { // > 4 then Ok Else to many overfloads...

           // Check if the RPM is higner than we can show in the display else show "out of range" in the display.
           if (uiTempDigi < 10000) {

               // A simple binary to BCD converter.
               for (cTempCnt = 4;cTempCnt > 0;cTempCnt--) {
                   ucCiffer[4 - cTempCnt] = (uiTempDigi % 10);
                   uiTempDigi = uiTempDigi / 10;
               }

               // Swap a leading zero with "blank"
               for (cTempCnt = 3;(ucCiffer[cTempCnt] == 0) && (cTempCnt > 0);cTempCnt--) {
                   ucCiffer[cTempCnt] = 0x0A;
               }

               // convert the BCD calue to port values.
               for (cTempCnt = 0;cTempCnt < 4;cTempCnt++)
                   ucLEDS[cTempCnt] = ~kucDigi[ucCiffer[cTempCnt]];

               // Flash the comma in the last digi if there is a signal input
               if (PIND.6 == 0) ucLEDS[0] &= ~(Dod);

           } else {
               // Show out of range in display
               if (PIND.6 == 0) ucLEDS[0] = ~(A+D+E+F+Dod);
               else ucLEDS[0] = ~(A+D+E+F);
               ucLEDS[1] = 0xFF;
               ucLEDS[2] = 0xFF;
               ucLEDS[3] = ~(A+B+C+D);
           }
       } else {
           // If we had to many overfloods (No signal) then we show that in the display
           // so it is visible for the user that there is no signal.
           if (PIND.6 == 0) ucLEDS[0] = ~(G+Dod);
           else ucLEDS[0] = ~(G);
           ucLEDS[1] = ~G;
           ucLEDS[2] = ~G;
           ucLEDS[3] = ~G;
       }
   };
}



Forumer™ is Voted #1 Free Forum Hosting provider
Build your own community today with the largest message board hosting company.