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 » RPM Program

November 06, 2010
by Icyhot
Icyhot's Avatar

I'm working on this program it brings up a couple of questions so I figure I'll just post my whole code since I'm new to this and it might slowly sprawl into a couple of topics. since this is about the 20th iteration of refinement in my program and I've made some observations which I might have attributed to incorrect assumptions or misconceptions.

I have cut a bunch of the comments that came from other nerdkits source code assuming others can find those comments here already.

// RPM_Count.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

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

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

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

    uint16_t counter =0;
    uint8_t c;
    uint8_t b;
    uint8_t dividedcount=0;
    uint16_t rpm;

void realtimeclock_setup() {
  TCCR0A |= (1<<WGM01);
  TCCR0B |= (1<<CS02) | (1<<CS00);
  OCR0A = 143;
  TIMSK0 |= (1<<OCIE0A);
}

volatile int32_t the_time;

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  the_time++;
  //2 blades per rev so dividedcount/2 happened during 10ms *100ms/s*60s/min
  rpm=dividedcount*3000;
  dividedcount=0;
}

int main() {
    // turn on interrupt handler
    sei();
    //start rtc
    realtimeclock_setup();

    //Set the following pins as inputs
    DDRB &= ~(1<<PB5); //PROX

    //start up the lcd
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
    //lcd_home();

     //Loops forever
  while(1) {
    c = (PINB & (1<<PB5))>>PB5; 
    b=c;

    lcd_home();
    //Note I've tried several combinations of formatting this, but it always displays goofy on the LCD currently it spits "sec" out on the 3rd line and not sure why the time is aligned to the right and not left.

    //lcd_write_string(PSTR("Time: "));
    //fprintf_P(&lcd_stream, PSTR("Time: %16.2f sec"), (double) the_time / 100.0);
    //fprintf_P(&lcd_stream, PSTR("Time: %16.2f sec"),the_time / 100);
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) the_time / 100.0);

    if (b==0){
    counter=counter++;
    dividedcount=dividedcount++;
    b=1;

    lcd_line_two();
    lcd_write_string(PSTR("RPM: "));
    lcd_write_int16(rpm);

    lcd_line_three();
    lcd_write_string(PSTR("DivCount: "));
    lcd_write_int16(dividedcount);

    lcd_line_four();
    lcd_write_string(PSTR("Ctr:"));
    lcd_write_int16(counter);
    lcd_write_string(PSTR(" in:"));
    lcd_write_int16(c);

        while (c==0){
    c = (PINB & (1<<PB5))>>PB5; 
    counter=counter;
    dividedcount=dividedcount;
    }//end while
    }//end if

  } //end of never ending while loop

  return 0;
}  //end of main function

As I re-read my code I realized one of my problems probably involves rpm doesn't have a chance to be output to LCD before I recalculate it. I realize putting my whole code out here typically opens it up to tons of criticism, but this is my first exposure to C.

Thanks in advance.

November 08, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

HI lcyhot,

You are definitely on the right track. Your initial thought that the writing to the LCD will be very slow is probably correct. My suggestion is to move the revolution counting into an interrupt using a pin change interrupt. See our keyboard and interrupts video tutorial for more on pin change interrupts. The structure of your code should look more like this

ISR(timer_interrupt){
  //calculate RPM given time interval
}

ISR(pin_change_interrupt){
  //tick up rev counter
}

main(){
  while(1){
     //perhaps do extra calculations
     //print current RPM to the screen
  }
}

I'm not really sure why you have that extra inner while loop in your main loop. If you explain your original reasoning for that I can try to offer pointers on a better way to achieve what you were going for. Other than that there are a few things you should clean up, like you assign b and c to the same thing, and then use them interchangeably. A good CS teacher would also tell you you should name your variables something more descriptive than just b and c, but I won't make you =).

Humberto

November 08, 2010
by Icyhot
Icyhot's Avatar

The reason b came about was because without it the whole time the sensor was triggered it would counter instead of just once. I thought about using the interrupt to count, but I never did quite understand what you meant by the ISR(vector). Namely the vector part. So I tried to ignore it :)

How do you distinguish between the timer and the sensor if you use the notation you describe.

does having 2 ISR functions not mess things up?

which does bring up a question of understanding on the realtimeclock example as well why did you get to use SIGNAL() vs ISR() and why does it output to the LCD as right aligned instead of coming in on the left of the screen? That is making my LCD display incorrectly as the word "sec" shows up on the 3rd line for some reason... I assume it has something to with running out of space on line one.

I agree to clean things up, and should have before sharing code but didn't...

Thanks for the response.

November 08, 2010
by Icyhot
Icyhot's Avatar

Okay so I made some progress tonight in understanding of interrupts. Namely the inside of the ISR() function parenthesis comes from the left column of http://www.nongnu.org/avr-libc/user-manual/groupavrinterrupts.html and was the meaning of the 2nd to last sentence on pg 70 (...reading the manual is often cryptic until if finally clicks what it meant, but so much new lingo in the last week)

So now my code is counting interrupts and clock cycles should be done right? ... not yet for some reason I'm displaying out negative numbers at times. also the LCD when it displays a bigger number that then goes smaller it leaves a ghostly image of the meaning less digits. Example if RPM reached 1538 and then slowed down to 532... the display might still show 5328 making the numbers seemingly more nonsense. Unfortunately I can't comprehend the numbers changing as fast as they do :) or is that fortunate so I don't have to try.

anyway here is my code up to now I have cleaned it up some, but it still isn't quite working.

// RPM_Count.c
// for NerdKits with ATmega168
// mrobbins@mit.edu

#define F_CPU 14745600

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

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

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

// Void is used for functions that do not return anything
    uint16_t counter =0;
    uint8_t test;
    uint8_t dividedcount=0;
    uint16_t rpm=0;
    uint16_t rpmout=0;
    uint8_t i;

void realtimeclock_setup() {
  // setup Timer0:
  TCCR0A |= (1<<WGM01);
  TCCR0B |= (1<<CS02) | (1<<CS00);
  OCR0A = 143; 
  TIMSK0 |= (1<<OCIE0A);
}

volatile int32_t the_time;

ISR(TIMER0_COMPA_vect) {
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
  //2 blades per rev so dividedcount/2 happened during 10ms *100ms/s*60s/min
  rpm=dividedcount*3000;
  dividedcount=0;
}//end interupt

void sensor_setup(){
  PCICR |= (1<<PCIE0);
  PCMSK0 |= (1<<PCINT5);
}

ISR(PCINT0_vect){
    if(PINB & (1<<PB5)) {
    counter=counter++;
    dividedcount=dividedcount++;
    }// end if
}// end ISR

int main() {
    // turn on interrupt handler
    sei();
    //start rtc
    realtimeclock_setup();
    //Set the following pins as inputs
    DDRB &= ~(1<<PB5); //PROX
    //Startup sensor interrupt
    sensor_setup();
    //start up the lcd
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

     //Loop forever
  while(1) {

    //try taking a 50 samples to average before writing rpm out
    for(i=0; i<50; i++){
        rpmout=rpmout+rpm/50;
        }// end for avg rpm

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) the_time / 100.0);

    lcd_line_two();
    lcd_write_string(PSTR("RPM: "));
    lcd_write_int16(rpmout);

    lcd_line_three();
    lcd_write_string(PSTR("DivCount: "));
    lcd_write_int16(dividedcount);

    lcd_line_four();
    lcd_write_string(PSTR("Ctr:"));
    lcd_write_int16(counter);
    lcd_write_string(PSTR(" rpm:"));
    lcd_write_int16(rpm);

  } //end of never ending while loop

  return 0;
}  //end of main function
November 09, 2010
by bretm
bretm's Avatar

Some issues I spotted:

uint16_t counter = 0;
uint8_t dividedcount=0;
uint16_t rpm=0;

These should be marked as volatile since you're updating them inside an interrupt routine and reading or modifying them outside the routine.

At 16 bits, rpm will overflow at 65536. That means dividedcount can't be greater than 21. If it overflows you might see negatives somewhere depending on how you use it. Is your RPM always less than that?

You're doing rpm=dividedcount*3000, and then using rpm/50. You could instead do rpm=dividedcount*60 and then not divide by 50 later. That will also improve the range allowed for dividedcount. rpm will never overflow because dividedcount will never exceed 255. (Is 255 enough of a limit for dividedcount?) But now it only represents 1/50th of the actual RPM.

Next problem:

for(i=0; i<50; i++){         
     rpmout=rpmout+rpm/50;         
}// end for avg rpm

This loop runs very fast, probably faster than 0.01 seconds. It will probably use the same value for "rpm" each time through the loop. At best it will see the value update once. Another problem is that that "rpm" is two bytes, and once in a long while the interrupt might update "rpm" in the middle of your loop reading the two bytes to add them together. This will cause incorrect and maybe seemingly impossible values to be read.

It seems like what you really want to do is take 50 samples 0.01 seconds apart, over a period of 0.5 seconds total. Is that right? In that case you can change your 0.01-second interrupt to add together 50 dividedcount values, and once the count reaches 50 you can multiply the total by 60 (equivalent to multiplying by 3000 and dividing by 50) to get the average RPM reading. Then set a boolean variable to tell the main loop that a new value is ready. The boolean (e.g. "char" or uint8_t) variable is only one byte so there won't be a race condition, and just make sure you read the result before the next 0.5 seconds elapses to avoid the race condition from that value getting updated.

ISR(TIMER0_COMPA_vect) 
{
   the_time++;
   dividedSum += dividedcount;
   dividedcount = 0;
   dividedSamples++;

   if (dividedSamples == 50)
   {
       rpm = dividedSum * 60;
       dividedSum = 0;
       dividedSamples = 0;
       rpmAvailable = 1;
   }
}

int main()
{
     // bunch of stuff
     // ...

     while (1)
     {
         while (!rpmAvailable)
             ;
         uint16_t rpmCopy = rpm;
         rpmAvailable = 0;
         // display rpm
     }
}

Next:

counter=counter++;     
dividedcount=dividedcount++;

should just be

counter++;
dividedcount++;
November 09, 2010
by Icyhot
Icyhot's Avatar

okay so I think one of my problems was RPM was changing too fast for me to read so I tried only outputting an update every time the clock counted to 50... still too fast? so i tried 256. I tried using the boolean method mentioned above and this is what I came up with, but the odd thing is even the else statement doesn't output anything.

Video

volatile int32_t the_time;
volatile uint8_t dividedcount=0;
volatile uint16_t rpm=0;
volatile uint8_t i;
volatile uint8_t rpmAvailable = 0;
volatile uint16_t counter=0;
volatile uint16_t rpmout=0;
uint8_t test;

void realtimeclock_setup() {
  TCCR0A |= (1<<WGM01);
  TCCR0B |= (1<<CS02) | (1<<CS00);
  OCR0A = 143; 
  TIMSK0 |= (1<<OCIE0A);
}//done setting clock

void sensor_setup(){
  PCICR |= (1<<PCIE0);
  PCMSK0 |= (1<<PCINT5);
}//done setting sensor

ISR(TIMER0_COMPA_vect) {
  the_time++;
  i++;
  //2 blades per rev so dividedcount/2 happened during 10ms *100ms/s*60s/min
  rpm=dividedcount*3000;

  if (i==256) {
    rpmAvailable=1;
    rpmout=rpm;
    i=0;
  }//end if

  dividedcount=0;
}//end interupt

ISR(PCINT0_vect){
    if(PINB & (1<<PB5)) {
        counter++;
        dividedcount++;
    }// end if
}// end ISR

int main() {
        sei();
    DDRB &= ~(1<<PB5);
    sensor_setup();
    realtimeclock_setup();
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    while(1) {

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) the_time / 100.0);

    lcd_line_two();
    if (rpmAvailable==1){
        lcd_write_string(PSTR("RPM: "));
        lcd_write_int16(rpmout);
        rpmAvailable=0;
    } else {
        lcd_write_string(" hello  ");
    }

    lcd_line_three();
    lcd_write_string(PSTR("DivCount: "));
    lcd_write_int16(test);

    lcd_line_four();
    lcd_write_string(PSTR("Ctr:"));
    lcd_write_int16(counter);
    lcd_write_string(PSTR(" i:"));
    lcd_write_int16(i);

  } //end while
  return 0;
}  //end of main function
November 09, 2010
by bretm
bretm's Avatar

It's not printing " hello " because the string isn't contained in PSTR().

It's never displaying "RPM: " which does have PSTR(). I'm not sure what's going on there. Maybe it's writing it but immediately getting overwritten by blanks from the errant "hello" string.

Your latest code still isn't averaging the 50 samples together. It's just taking the most recent sample after i reaches 256. rpm will always be an exact multiple of 3000.

Let's get simpler:

#define SAMPLES_MAX 50    // must be divisible into 3000

volatile uint8_t samples = 0;
volatile uint16_t count = 0;

ISR(TIMER0_COMPA_vect) {
    samples++;
}//end interrupt

ISR(PCINT0_vect){
    count++;
}// end ISR

int main() {
    sei();
    DDRB &= ~(1<<PB5);
    sensor_setup();
    realtimeclock_setup();
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    while(1) {

         // wait for 50 samples
         while (samples < SAMPLES_MAX)
            ;

         // copy the result and reset the state
         cli();
         uint16_t result = count;
         count = samples = 0;
         sei();

         // calculate RPM from
         // result = (2*rotations) per (0.01sec * SAMPLES_MAX)
         uint16_t rpm = result * (3000 / SAMPLES_MAX);

         lcd_home();
         printf_P(&lcd_stream, PSTR("%5d rpm"), rpm);

  } //end while
  return 0;
}  //end of main function
November 10, 2010
by Icyhot
Icyhot's Avatar

Many Thanks to Bretm and Humberto

I was not trying to average out my rpm over samples as much as troubleshoot. This code has some accuracy issues that I'll be looking to refine, but for the moment it feels like success and progress. By accuracy issues I mean that since I've only got 2 pickups per rev so say Dividedcount=5 then rpm = 1,500, but if divcount=6 then rpm = 1800. I ran my drill and clocked a reading on the LCD and I measured it in Hz with my multimeter.

My snapshot was 51hz 1,500 rpm and divcount=5 so my check was that 51hz /2 (teeth/rev) = 25.5 (1/s) and * 60 s/min = 1530rpm so within 2%, but as you can tell if the drill ran between 1,500 it wouldn't be accurate again until it ran 1800rpm as my dividedcount/time and number teeth limit how fast I can read.

I have posted my code for the next person to pickup and run with.

// RPM_Count.c
// for NerdKits with ATmega168
// ICYHOT AND SMOKEYBURNOUT
// THANKS TO BRETM AND HUMBERTO

#define F_CPU 14745600

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

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

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

// Void is used for functions that do not return anything
// the_time will store the elapsed time
// in hundredths of a second.
// (100 = 1 second)
// 
// note that this will overflow in approximately 248 days!
//
// This variable is marked "volatile" because it is modified
// by an interrupt handler.  Without the "volatile" marking,
// the compiler might just assume that it doesn't change in 
// the flow of any given function (if the compiler doesn't
// see any code in that function modifying it -- sounds 
// reasonable, normally!).
//
// But with "volatile", it will always read it from memory 
// instead of making that assumption.
volatile int32_t the_time;
volatile uint8_t dividedcount=0;
volatile uint16_t rpm=0;
volatile uint16_t i;
volatile uint8_t rpmAvailable = 0;
volatile uint16_t counter=0;
volatile uint16_t rpmout=0;
volatile uint8_t test;

void realtimeclock_setup() {
  // setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 143; 
  //OCR0A = 71999;//OCR0A IS ONLY 8 BIT!!!!  COUNTING TO 500
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
}//done setting clock

void sensor_setup(){
  //Enable PIN Change Interrupt 1 - This enables interrupts on pins
  //PCINT14...8 see p70 of datasheet
  PCICR |= (1<<PCIE0);
  //Set the mask on Pin change interrupt 0 so that only PCINT5 (PB5) triggers
  //the interrupt. see p71 of datasheet
  PCMSK0 |= (1<<PCINT5);
}//done setting sensor

//SIGNAL(SIG_OUTPUT_COMPARE0A) {  this is the old tag
ISR(TIMER0_COMPA_vect) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
  i++;
  //2 blades per rev so dividedcount/2 happened during 10ms *100ms/s*60s/min

  if (i==10) {
    rpmAvailable=1;
    rpm=dividedcount*300;
    rpmout=rpm;
    test=dividedcount;
    i=0;
      dividedcount=0;
  }//end if
}//end interupt

ISR(PCINT0_vect){
    if(PINB & (1<<PB5)) {
        counter++;
        dividedcount++;
    }// end if
}// end ISR

int main() {
    // turn on interrupt handler
    sei();
    //Set the following pins as inputs
    DDRB &= ~(1<<PB5); //PROX
    //Startup sensor interrupt
    sensor_setup();
    //start rtc
    realtimeclock_setup();
    //start up the lcd
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    //Loop forever
  while(1) {

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) the_time / 100.0);

    lcd_line_two();
    if (rpmAvailable==1){
        lcd_write_string(PSTR("RPM: "));
        lcd_write_int16(rpmout);

        lcd_line_three();
        lcd_write_string(PSTR("DivCount: "));
        lcd_write_int16(test);
        rpmAvailable=0;
    }

    lcd_line_four();
    lcd_write_string(PSTR("Ctr:"));
    lcd_write_int16(counter);
    lcd_write_string(PSTR(" i:"));
    lcd_write_int16(i);

  } //end of never ending while loop

  return 0;
}  //end of main function
November 20, 2010
by Icyhot
Icyhot's Avatar

So here is where we ended up and it's running enjoy and you can see lights change and they are also dependent on the pot adc reading.

// RPM_lookup.c
// for NerdKits with ATmega168
// ICYHOT AND SMOKEYBURNOUT
// THANKS TO BRETM AND HUMBERTO
#define F_CPU 14745600
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <inttypes.h>
#include "../libnerdkits/delay.h"
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/uart.h"
volatile uint32_t rpmtime=0;
volatile uint16_t rpm=0;
volatile uint8_t i=0;
volatile uint16_t pot=0;
volatile uint16_t temp=0;
uint8_t lookupvalue=0;
int lookup[2][2] PROGMEM = {
    {4,8},
    {2,16}
    };
void realtimeclock_setup() {
   TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  OCR0A = 11; 
  // (14400 / 11 = 1200 per second)
  TIMSK0 |= (1<<OCIE0A);
}//done setting clock
void sensor_setup(){
  PCICR |= (1<<PCIE0);
  PCMSK0 |= (1<<PCINT5);
}//done setting sensor
void adc_init() {
    ADMUX = 0;
    //to be enabled, with a clock prescale of 1/128
    //so that the ADC clock runs at 115.2kHz
    ADCSRA = (1<<ADEN)|(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);
    //fire a conversion just to get the ADC warmed up
    ADCSRA |= (1<<ADSC);
}
ISR(TIMER0_COMPA_vect) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  rpmtime++;
}//end interupt
ISR(PCINT0_vect){
    if(PINB & (1<<PB5)) {
        i++;
            if (i==2) {
            rpm=72000/rpmtime;
            rpmtime=0;
            i=0;
            }//end if
    }// end if
}// end ISR
uint16_t adc_tempread() {
    ADMUX = 0;
    while(ADCSRA&(1<<ADSC)){
    //waiting for ADC to finish conversion
    }
    //read from the ADCL/ADCH registers, and combine the result
    //note: ADCL must be read first (datasheet pp.259)
    uint16_t result = ADCL;
    uint16_t temp = ADCH;
    result = result + (temp<<8);
    //set ADSC bit to get the *next* conversion started
    ADCSRA |= (1<<ADSC);
    return result;
}
uint16_t adc_potread() {
    ADMUX = 5; // Datasheet PG256
    while(ADCSRA&(1<<ADSC)){
    //waiting for ADC to finish conversion
    }
    //read from the ADCL/ADCH registers, and combine the result
    //note: ADCL must be read first (datasheet pp.259)
    uint16_t result = ADCL;
    uint16_t temp = ADCH;
    result = result + (temp<<8);
    //set ADSC bit to get the *next* conversion started
    ADCSRA |= (1<<ADSC);
    return result;
}
int main() {
    // turn on interrupt handler
    sei();
    //Set the following pins as inputs
    DDRB &= ~(1<<PB5); //PROX
    //Set outputs
    DDRB |= (1<<PB4); //Green
    DDRB |= (1<<PB2); //Yellow
    DDRB |= (1<<PB1); //Red
    DDRB |= (1<<PB3); //white
    //Initializer functions
    sensor_setup();
    realtimeclock_setup();
    adc_init();
    lcd_init();
    FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);

    //Loop forever
  while(1) {
    if (pot<512&&rpm<1000){
    lookupvalue = lookup[0][0];//yellow
    }
    if (pot>512&&rpm<1000){
    lookupvalue = lookup[1][0];//red
    }
    if (pot<512&&rpm>1000){
    lookupvalue = lookup[0][1];//white
    }
    if (pot>512&&rpm>1000){
    lookupvalue = lookup[1][1];//green
    }
    PORTB = lookupvalue;

    lcd_home();
    fprintf_P(&lcd_stream, PSTR("%16.2f sec"), (double) rpmtime / 1200.0);

    lcd_line_two();
    fprintf_P(&lcd_stream, PSTR("RPM: %4u"),rpm);

    lcd_line_three();
    lcd_write_string(PSTR("Lookup: "));
    lcd_write_int16(lookupvalue);

    lcd_line_four();
    pot = adc_potread();
    temp = adc_tempread();
    fprintf_P(&lcd_stream, PSTR("temp: %4u pot:%4u"),pot,temp);

  } //end of never ending while loop

  return 0;
}  //end of main function
November 21, 2010
by hevans
(NerdKits Staff)

hevans's Avatar

Hi Icyhot,

I'm glad you got it working. If you can post a video of the working RPM counter, or some pictures we would definitely like to see them.

Humberto

March 01, 2011
by Earnhardt
Earnhardt's Avatar

Hi all, Maybe someone can help me; I am new to the NerdKits, I have a back ground network Infrastructure but programming isn’t my strong point. I made an Inertia dynamometer for a motorcycle which I have been trying to find a simple way to get RPM, Torque, Horsepower to be displayed on GLCD monitor. I was told I could connect two Hall Effect sensor on the drum, not sure how true that is, But I do know that I can connect one up to get RPM; but not sure how I would connect two and program the MCU to give me this information. Yes I know I can just buy one but I think it would be cool to build the electronics and make this myself.

March 01, 2011
by Icyhot
Icyhot's Avatar

The hall effect sensor is an on/off switch and not sure about two sensors, but definitely multiple magnets as seen here. The reason you use multiple magnets is because the bigger the object spinning the less resolution you will have when the speed changes. You could also use a reluctor wheel and induction proximity switch setup in which the sensor picks up teeth. I used a spade bit and prox switch and basically you have to find a seperate voltage supply to power the sensor and a 9V or equivalent for your micro. you will have a signal line coming off your prox and depending on what kind you get it will either stay low and go high or vice versa. now if your signal line is sending out more than 5V your micro can not handle the input so you have to make a voltage divider to protect your micro and send the correct voltage in. then as in my code you set up your digital line and listen for an interrupt to trigger and keep counts and divide by the time it took the counts to occur.

March 10, 2011
by Earnhardt
Earnhardt's Avatar

Lcyhot, Hi there

And thanks for the information; please bear with me because I am new at this stuff and not up to speed on the programming lingo are what I am looking at in the code. I am just using what code that I got with the Nerdkit and code other people provided as far as me know what line to change and what to put in its place I am not there yet.  So you said in the code set up the digital line and listen for an interrupt to trigger and keep counts and divide by the time it took the counts to occur. Not sure what lines to change are what to add to do that.
September 07, 2016
by Ralphxyz
Ralphxyz's Avatar

Anyone ever test this code.

Anyone have alternate RPM code that you know works?

I have some of the TCRT5000 IR Photoelectric Sensor coming.

I need RPM for my mill and my lathes.

Sure appreciate any help.

Ralph

September 07, 2016
by sask55
sask55's Avatar

Hi Ralph

It has been a few years since I have used inferred Photoelectric sensors so it is unlikely I will be of much help.

I see that the sensors you have coming are complete with a high line voltage 10 A relay board. I don't think you will want to use that relay board for a micro based RMP monitor application. The relay maybe required to open and close many time per second which is likely going to cause problems and is completely unnecessary for the application. All that is required is to supply power thru a current limiting resistor to the LED side of the reflective sensor and monitor output from the phototransistor with an input pin on the mIrco. You will require some IR reflective marking on the equipment you intend to read the RPMS from. The IR reflective spectrum may not be the same as the visible spectrum that we see. Red color may be best for reflector marks whereas cyan may be best for absorbing IR. It may be best to have a number of marks equally spaced around the input shaft/ wheel. Perhaps existing teeth on a sprocket or gear or spokes on a wheel would be a convenient place to pick up a reflection. As you know the micro is capable of reading thousands of events each second and doing the necessary calculations to display RPM.

When I was playing around with the IR reflective sensors I was reading off 8 and 10-bit gray scale printed wheels. The system I was using was capable of reading the position of the wheel to 1 degree of rotation by constantly polling the phototransistors output state of up to 10 sensors. That was a very slow speed rotation and a somewhat different application using the same type of sensors. I was not using the ADC to read the sensors but reading directly to a micro pin set up as an input pin. I think you could use a pin change interrupt or a pin polling system to establish the intervals between sensor hits. I was using OPB609 series sensors they worked very well as I recall.

If you have any specific questions I may be able to help a bit. I could try and find some of the code I was using at the time. From the short look I took at the code here I think it should work it will require some modification to handle the pin change data flow.

Darryl

September 07, 2016
by Ralphxyz
Ralphxyz's Avatar

Thanks Daryl for the reply, you had a big rpm project for your mower.

I think you used magnetic sensors, this is for my mill and lathe so I do not think using magnets would be good but a pulse is a pulse so I am looking for your project.

Ralph

September 08, 2016
by sask55
sask55's Avatar

You are correct I have a monitor system on my mower that is using magnetic hall effect type sensors. That system does not actually calculate RPM on any of the 6 sensors although it would be very straight forward to do so. My mower shaft monitor constantly measures and compares the duration of time each shaft takes between each successive pulse. Then if any shaft is out of time with the main PTO shaft and audible buzzer sounds and an indication led is lit. This approach allows the PTO speed to vary up and down to any speed without triggering any alarms.

In your project I think you could use and would want a much faster pulse rate with many pulses in each revolution of the shaft. From the input pins point of view on the micro it is basically the same type of signal that is being read. I think that there are at least a couple of ways that you can connect the phototransistor circuit to get a usable input signal. Common-emitter or common collector are both possible. I have a short application note for Fairchild AN-3005 printed out. I think you should be able to find this document at www.fairchildsemi.com. I general idea is to have a connection to the input pin that changes state from high to low or possibly low to high depending on your choice of circuits. as the phototransistor detects or does not detect a reflection of the IF LED. The IR led is mounted on the sensor chip very near the phototransistor in such a way that the IF light will not be seen directly by the sensor phototransistor only reelected IF light bouncing off a nearby IR reflective service will be detected.

The light that is emitted from the LED on the sensor chip is usually not visible to the human eye. You can often use a digital camera to see if the LED is actually emitting IR light. Some cameras may have an IR filter on the lens so they may not be useful here, but if you look at the IR LED when it is powered using a cell phone camera, web cam, or digital SRL you will likely see that is it glowing. This can be a handy way to trouble shoot the system since you will never get a responses back from the phototransistor if the LED is not emitting IR. You can start by simply connecting the circuit then test if you are indeed getting a response from the phototransistor by placing and removing a IR reflective surface very near the sensor. Once you are confident that the sensor is sending a reliable input signal to the input pin on the micro you could make a couple of changes to the software in this thread to read out RPMs. You will then need to mount the sensor very near the rotating shaft/wheel that you intend to monitor. The shaft/wheel will have to have an evenly spaced set of IR reflective marks or surfaces on it that trigger a change in the state of the input pin as each mark/tooth/spoke goes by the sensor. In my experience most metal surfaces are reasonably good IR reflective, so just a metal tooth passing by near the sensor will trigger a strong reliable input signal. This can be determined at very slow speed by slowly turning the shaft by hand and setting up a bit of software on the micro to indicate the state of the input pin. Once you are confident that the input is reliable changing the software to read many time per second and spinning the shaft at high speeds will determine if everything is working as expected.

September 08, 2016
by Ralphxyz
Ralphxyz's Avatar

Thanks Darryl, why are your post so huge? I have to keep scrolling to the right to read your post and then back for the next line. You are typing 1059 characters per line, very strange.

Anyway, why would I need to detect each tooth why not just once per revolution?

I suppose it might be more accurate having a high pulse count but do I really need that much precision? I can easily put one reflector on my spindle pulley it would be kinda hard to do multiple pulses unless I removed the pulley to work on it. For one pulse I can just stick some reflective tape on the pulley and count the pulses.

How is your monitor system working? You have a couple of years on it now, that was such a interesting project.

You are right I would not be using the relay, actually the only reason I purchase that one was because of the sensors being mounted on the cable. That makes a really nice small component to mount on my mill and lathes. I could even remove the PCB and electronics and just use the sensors going to my Nerdkit. I might need a resistor for the led if one is not integrated in the cable.

Ralph

September 08, 2016
by sask55
sask55's Avatar

I don't have any idea why the text on this thread does not seam to word wrap to fit the screen. I don't know about your view but this entire thread and only this thread seams to be that way. I have never had this happen on any other part of the NK Forums.

It is kind of annoying to say the least. perhaps I have something set incorrectly I don't know, I thought it was something else on this page that was causing the issue.

I think you would get a bit better stability of reading with more then one trigger mark around the shaft. I don't think it is going to be a big concern ether way. You may have to do a little experimenting with the IR sensor in order to achieve adequate results. The pulley surface may be quite IR reflective you may get more distinct signals if you paint or cover the metal with a IR absorbing material or color. It may be possible that tape made to be very reflective of visible light is not so reflective of IR. With a bit of trial and error moving the sensor closer and father from the surface while monitoring the state of output it should be easy to get a idea how reflective a surface is. You will want a very district difference between your two surfaces, since that is important for reliable readings.

My mower monitoring system has worked well. We typically only use the mower in the early spring and late in the fall when the crops are not in the fields. After harvest we will be putting it to use again.

September 09, 2016
by Rick_S
Rick_S's Avatar

I created a copy of this on my forum - It makes it a bit easier to read and follow in case anyone is interested.

I'll leave it there unless asked to remove it by either the originator of the thread or one of the NK staff.

You can see it at rs-micro forums

Rick

September 09, 2016
by Ralphxyz
Ralphxyz's Avatar

Examples I have seen on the web just did a splash of white for a single marker. Some used shiny metal foil tape.

I can paint the pulley flat black and then try a metal tape and white spot.

I just cannot picture making more than one mark unless I removed the pulley from the spindle. I would like to find an example and discussion of multi mark versus single mark.

I have seen one project on the web that used a op-amp comparator to get a strong signal when the mark was detected that would be relatively easy to do.

I will eventually remove the pulley as I have to rebuild the spindle but that will not be soon.

Once I get my sensor I'll play around with it and find something that works well.

Also I will have to choose rather I am looking for the transition to a high or to a low.

I am not sure what icyhot used.

Ralph

September 19, 2016
by Ralphxyz
Ralphxyz's Avatar

Well, I am to lazy to wire up the LCD for a Nerdkit and my I2C backpack is not working so I looked at using Arduino.

I have some Arduino Nanos that are really nice and small.

I have finally found some code on line that actually works so I am able to count the rpms using the IR sensor.

I just have a piece of white card spinning on the motor, the sensor seems to be detecting it fine.

I am holding the motor in my hand so not getting good/steady results.

Now I need my bluetooth modules to show up and I can play with them.

And to wire up the ACS712 amperage module.

Ralph

September 21, 2016
by BobaMosfet
BobaMosfet's Avatar

Ralph-

Sorry, didn't see this until now. Doesn't matter if it's IR, magnetic (hall), Greycode or any other mechanism to tell RPM-- it will all work reliably.

The fan in most PCs now has 3-wire, which happens to be 2 for power and one for a pulse-- they use a hall-sensor to generate a pulse once every time around.

Here is how you algorithmically get RPM reliably--

  1. Create a 1-minute timer
  2. Create a small circuit to read a pulse, use a schmitt-trigger to clean the pulse up, and put that circuit's output as an input into the micro-controller on a pin-triggered interrupt (leading edge).
  3. Every time that interrupt fires, check to see if the counter needs zero (has been read once already) and if so zero it, and then simply do a +1 on your counter.
  4. When your 1-minute timer expires, set a flag that the counter needs zeroed prior to the next increment, and then display the count on the display.

The reason I use the 'flag' concept is it's a) very fast, and b) it prevents your counter reset from being clobbered by your increment interrupt.

You're not concerned with position, you're concerned with rate of rotation-- that is a much easier (less precise) problem than position, although the solutions are similar.

Very, very simple.

BM

September 23, 2016
by Ralphxyz
Ralphxyz's Avatar

Hey BM thanks, I decided to use a Arduino and finally found some actual working code.

So I have a tachometer working, I am going to makeup a rigid setup for and will

finally learn how to power a DC brushless motor so I can test some higher rpms.

This is another subject:

I purchased a Consew Variable Speed sewing machine motor. It is 3/4 hp and reportedly has tremendous torque in slower rpms.

I will be using one on my vertical mill and on my lathe and possible on my band saw as well.

The motor has a foot switch which uses hall sensor to turn on off slowly and rotate up to the set speed.

I wanted to replace the foot switch with a on/off switch.

I will probe the connections with my DVM and possible my Oscope to see if I can identify the wiring.

I'll be back for more help with this.

Ralph

Post a Reply

Please log in to post a reply.

Did you know that interrupts can be used to trigger pieces of code when events happen? Learn more...