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.

Sensors, Actuators, and Robotics » Codewheel and RPM

June 02, 2012
by DaveGriff
DaveGriff's Avatar

First of all this is my first ever post so you'll have to bear with me :)

I have recently bought a codewheel that has 500 CPR (Counts Per Revolution) Here's a link to the Data Sheet: http://www.farnell.com/datasheets/20523.pdf

At the moment I've set it up attached to a motor, It has 2 channels; one is the indexing channel that pulses once per revolution and the other pulses 500 times per revolution.

Here is the current set-up: Current set-up

I have set-up a pin change interrupt on PC5 that increments a counter by 1 every time it fires, then I look at how many counter ticks have occurred between the pulses and from that I can calculate the RPM:

Counting RPM

The problem I'm having is that I can only use the indexing channel as the other channel pulses too fast at high RPM and the microprocessor cannot count fast enough (3000 RPM * 500 CPM = 1500000 CPM) or 25000 counts every second!

I think the best war to solve this problem is to use the 16-bit counter and set it up so that it fires an interrupt when the count reaches 100, that way I might have a chance of counting RPM when it is high, but I don't know how to set up the counter.

If anyone could point me in the right direction I would appreciate it.

June 02, 2012
by pcbolt
pcbolt's Avatar

Hi Dave -

Cool gizmo and a nice set-up you have there. The 16-bit timer works the same as the 8-bit, except it can top out at 65,536 counts and you can set a compare value at any range in between. However, it doesn't count any faster than the 8-bit timer. The resolution of 25000 counts per second can easily be clocked by the MCU, in fact it is sittting around waiting >500 CPU cycles before the next input comes in. What I would do is set the 8-bit timer up in "normal" mode which simply counts from 0 to 255 then "overflows" and starts counting again from 0. The good part though, is when it overflows it can trigger an interrupt where you can just increment a count variable to track the number of overflows. To get the number of CPU "ticks" you just get the current timer count (from TCTN0) and add the overflow count times 256

total_ticks = current_count + (overflow_count * 256)

Of course now you need to know how long a "tick" is in seconds. If you set the timer "prescaler" to 1, the time per tick is 1/14,745,600 seconds or 67.817 nanoseconds (assuming you are using the oscillator from the Nerdkit).

Using the timer in "normal" mode, most of the settings you need are already set by default. You only need to set the "prescaler" value and enable the "overflow" interrupt like this:

TCCR0B |= (1<<CS0);      // set clock prescaler to 1
TIMSK0 |= (1<<TOIE0);    // enable overflow interrupt

All you need is an interrupt service routine like this one:

ISR(TIMER0_OVF_vect){
overflow_count++;    // declare this volatile and global uint32_t
}

Couple of things to keep in mind. Since the counter is incrementing so fast, you will be dealing with large numbers. If you use floating point division, the MCU can't deal with more than about 7 significant digits so you will have to code accordingly. Also, I would reset the timer variables to 0 at the start of each timing sequence.

June 03, 2012
by DaveGriff
DaveGriff's Avatar

hi,

Thanks for the reply, I just started to code this when I came across a problem, are all the pins for counter 0 being used by the LCD?

I don't know if I'm being silly here but how can I get around this?

(Very nice solution BTW)

June 03, 2012
by pcbolt
pcbolt's Avatar

Dave -

The pins for timer/counter 0 (OC0A/OC0B => PD5/PD6) are used for the LCD. They are enabled when you set them as output (i.e. DDRD |= (1<<PD5)) and also when you set up the timer/counter 0 as anything BUT "normal" mode. So in "normal" mode you will not have to worry. The timer itself operates internally and only outputs when needed.

It is an interesting question because when the LCD is initialized, both PD5 and PD6 are set as output. Using timer/counter 0 in "clear on compare match" mode (like in the "realtimeclock" program) PD5 is being set or cleared every 100th of a second. PD5 turns out to be an LCD data pin but it won't be transferred to the LCD memory until the PD6 pin (enable) goes high. It's possible, but not likely, you could write data to PD5, the timer changes it, then PD6 gets enabled, and your data would be corrupted. The situation would be far worse if you enabled OC0B as a timer/counter output, then data would be sent on every compare match.

If you want the output mode, you could switch to timer/counter 2 which is also 8-bit and has an output on PB3 (OC2A).

Far from a silly question.

June 03, 2012
by esoderberg
esoderberg's Avatar

Dave,

PCBolt almost surely has the best advice, to use timer/counter 2 and leave the LCD running off PORTD. However, it is not too hard to change the lcd pins. Below is code I used to change the output pins for the LCD from the Nerdkit standard PD2-7 to PD0-4, PB5, and PD7 (PD5,6 on the pololu controller I was working with used those pins to control a built in H-bridge so they weren't available for the LCD). If you follow the format used to make those changes you can see that pretty much any mix of I/O pins can be used. The updated LCD.C file below would go into the libnerdkit file in place of the original version if you wanted to change your LCD I/O pins.

// lcd.c
// Based on nerkits code, with lcd pin outputs modified to work with available outputs on pololu baby orangutan/atmega328p
// E. Soderberg
//
// PIN DEFINITIONS:
//
//
// PD0,1,2-4 -- LCD DB4-7 (pins 11-14) 
// PB5 -- LCD E (pin 6) 
// PD7 -- LCD RS (pin 4)

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

// lcd_set_type_data()
void lcd_set_type_data() {
  PORTD |= (1<<7);
}

// lcd_set_type_command()
void lcd_set_type_command() {
  PORTD &= ~(1<<7);
}

// lcd_write_nibble(...)
void lcd_write_nibble(char c) {

  // NOTE: only 2 or 3 work in the delays here.

  // set data
  PORTD &= ~(0x17);//~0x17 =  11101000 ie turns off data pins PD4,2,1,0

  //c is 4 input bits, they will be used to turn on PORTD pins on PD4, 2,1,0 respectively if c is high (1) - puts 1111 into 10111 portd pins
  PORTD |= ((c&0x08) << 1) | (c&0x07);

  // E high
  PORTB |= (1<<5);
  delay_us(1);
  // E low
  PORTB &= ~(1<<5);
  delay_us(1);

}

void lcd_write_byte(char c) {
  lcd_write_nibble( (c >> 4) & 0x0f );
  lcd_write_nibble( c & 0x0f );
  delay_us(80);
}

void lcd_clear_and_home() {
  lcd_set_type_command();
  lcd_write_byte(0x01);
  delay_ms(50);
  lcd_write_byte(0x02);
  delay_ms(50);
}

void lcd_home() {
  lcd_set_type_command();
  lcd_write_byte(0x02);
  delay_ms(50);
}

void lcd_write_data(char c) {
  lcd_set_type_data();
  lcd_write_byte(c);
}

// lcd_write_int16
void lcd_write_int16(int16_t in) {
  uint8_t started = 0;

  uint16_t pow = 10000;

  if(in < 0) {
    lcd_write_data('-');
    in = -in;
  }

  while(pow >= 1) {
    if(in / pow > 0 || started || pow==1) {
      lcd_write_data((uint8_t) (in/pow) + '0');
      started = 1;
      in = in % pow;
    }

    pow = pow / 10;
  }

}

// lcd_write_int16_centi
// assumes that its measured in centi-whatevers
void lcd_write_int16_centi(int16_t in) {
  uint8_t started = 0;

  uint16_t pow = 10000;

  if(in < 0) {
    lcd_write_data('-');
    in = -in;
  }

  while(pow >= 1) {
    if(in / pow > 0 || started || pow==1) {
      lcd_write_data((uint8_t) (in/pow) + '0');
      started = 1;
      in = in % pow;
    }

    if(pow == 100) {
      if(!started) {
        lcd_write_data('0');
      }
      lcd_write_data('.');
      started = 1;
    }

    pow = pow / 10;
  }

}

void lcd_write_string(const char *x) {
  // assumes x is in program memory
  while(pgm_read_byte(x) != 0x00)
    lcd_write_data(pgm_read_byte(x++));
}

void lcd_goto_position(uint8_t row, uint8_t col) {
  lcd_set_type_command();

  // 20x4 LCD: offsets 0, 0x40, 20, 0x40+20
  uint8_t row_offset = 0;
  switch(row) {
    case 0: row_offset = 0; break;
    case 1: row_offset = 0x40; break;
    case 2: row_offset = 20; break;
    case 3: row_offset = 0x40+20; break;
  }

  lcd_write_byte(0x80 | (row_offset + col));
}

void lcd_line_one()   { lcd_goto_position(0, 0); }
void lcd_line_two()   { lcd_goto_position(1, 0); }
void lcd_line_three() { lcd_goto_position(2, 0); }
void lcd_line_four()  { lcd_goto_position(3, 0); }

// lcd_init()
void lcd_init() {
  // set pin driver directions
  // (output on PD7,PD4, and PD2,1,0 and PB5)
  DDRD |= 0x97;//10010111
  DDRB |= 0x20;//00100000

  // wait 100msec
  delay_ms(100);
  lcd_set_type_command();

  // do reset
  lcd_write_nibble(0x03);
  delay_ms(6);
  lcd_write_nibble(0x03);
  delay_us(250);
  lcd_write_nibble(0x03);
  delay_us(250);

  // write 0010 (data length 4 bits)
  lcd_write_nibble(0x02);
  // set to 2 lines, font 5x8
  lcd_write_byte(0x28);
  // disable LCD
  //lcd_write_byte(0x08);
  // enable LCD
  lcd_write_byte(0x0c);
  // clear display
  lcd_write_byte(0x01);
  delay_ms(5);
  // enable LCD
  lcd_write_byte(0x0c);
  // set entry mode
  lcd_write_byte(0x06);

  // set cursor/display shift
  lcd_write_byte(0x14);

  // clear and home
  lcd_clear_and_home();
}

int lcd_putchar(char c, FILE *stream) {
  lcd_write_data(c);
  return 0;
}
June 05, 2012
by Ralphxyz
Ralphxyz's Avatar

So DaveGriff, where did you get the codewheel from?

Ralph

June 06, 2012
by DaveGriff
DaveGriff's Avatar

I got it from Farnell there are a few different types on there

Dave

June 06, 2012
by Ralphxyz
Ralphxyz's Avatar

What a strange site Farnell:

They do not appear to have a USA distributor.

I see a Great Britain source for 18 euros but I do not know what the shipping might be!

Ralph

June 06, 2012
by Rick_S
Rick_S's Avatar

I believe Newark is the US Farnell.

June 06, 2012
by Ralphxyz
Ralphxyz's Avatar

Well if so I cannot find a listing for any codewheels, guess I'll just do a google search to see what shows up.

Ralph

June 06, 2012
by Rick_S
Rick_S's Avatar

Codewheel link

Encoder sold separately. Here I think

June 06, 2012
by Ralphxyz
Ralphxyz's Avatar

Darn those prices are a bit steep.

I think I have a codewheel drawing that I could print on a transparency and paste on a clear vinyl disk.

I don't know what the resolution would be and I don't think I could determine rotation direction but I could possible get rpm.

Ralph

June 07, 2012
by Rick_S
Rick_S's Avatar

Tear apart an old ball mouse, or get an old arcade trackball or atari tempest controller. They all have code wheels and pickups. It's a much cheaper alternative.

July 06, 2012
by bluestang
bluestang's Avatar

I think You will also find them in printers, so pick one up alongside the road tear it apart and you will find a code wheel and chipset, I like Hp they usually have 2 of them in their with a motor as well.

July 07, 2012
by Ralphxyz
Ralphxyz's Avatar

I took apart a ball mouse, there is a code wheel but I would be really pressed to figure out a way to use it.

Ralph

July 11, 2012
by pcbolt
pcbolt's Avatar

Ralph -

Break out the old O-scope. Or, if you haven't torn the mouse completely apart, check out the PS2 interfacing. I wrote up a library article on it....at your request mind you :-)

July 11, 2012
by Ralphxyz
Ralphxyz's Avatar

:-) Thanks pcbolt.

That is a great PS2 interfacing article.

The problem I have with using a ball mouse code wheel is with how to physically connect one to say a motor shaft.

The code wheel is about 3/8" with a little -1/8" stub for mounting to a shaft and then aligning the led would be a challenge.

Does any one have any pictures of actually doing this?

Ralph

Post a Reply

Please log in to post a reply.

Did you know that you can connect to certain car computers via the OBD-II port with a microcontroller? Learn more...