Cat Wheel Speedo

a black cat with a yellow collar sits in an upright wheel about 1m high.

Here is a basic speedometer/odometer for your cat wheel. It measures the speed in m/s, and keeps the total distance travelled while it’s powered on.

You will need:

  • double sided tape to attach the magnets and the switch to the wheel eg 3M tape
  • wires, power source etc
  • a cat

Fury, a black cat, sticking his tongue out

I did have some extremely cheap magentic switches lying about, the kind that are used on alarm systems to signal if a door or window has been opened or closed. But they didn’t work great, so while the reed switch module looked a bit overkill, it worked much better.

ESP32 or Arduino?

I prefer ESP32s to Arduino these days. Most of them have pins with built-in resistors, they come with wifi and many modules have BLE, they tend to be cheaper than Arduino and they come in a vast array of shapes and sizes. You can program exactly the same as an Arduino with the IDE. Have a search online for guides on how to do this.

Setting up

You need to find a place where the magnets can be stuck at equal distances around the outside of the wheel, and you need to find a spot where the reed switch sensor can sit so the magnets pass very close by without knocking it. My cat wheel had a handy bit at the base and raised sections all around the edge of the wheel. I used a 30cm long 3-core cable to connect the reed switch module to the ESP32, so the ESP32 could sit somewhere comfortably.

Connect the reed switch to 3.3V, Ground and the DO or Data pin to a data pin on your ESP32, eg, pin 19.

ESP32 Reed Switch
3.3v V+
GND GND
GPIO 19 DO

Now measure the diameter of the inside of the wheel, I used a laser measure so super accurate - 1.08m. The circumference is π times the diameter, in my case, 3.4m. I divide this by 4, because I have 4 magnets on the wheel, which gives us a value of 0.85.

This value goes in the following line:

const float wheelCircumference = 0.85;

Connect up an I2C LCD unit. It needs 5v, GND and SCL and SDA pins. Look up a pinout of your ESP32 model to find out which those are; it’s usually pins 21 and 22 on ESP32. Once connected you can adjust the potentiometer at the back to change the contrast.

ESP32 LCD I2C
5V V+
GND GND
SCL (21) SCL
SDA (22) SDA

Here it is all connected up and put in a nice plastic box:

A translucent blue box containing a small form ESP32 on a solderless breadboard, with various wires and connectors. the back of the lid has the LCD unit connected to it.

That’s all it needs - the code below (or download here) will increment a count every time a magnet passes a sensor. It will multiply that by the value you entered in wheelCircumference to work out speed and distance.

Code

I have it set so it sends all the information to the serial monitor: Number of revolutions, the count, the speed and the distance. Underneath is the code for the LCD display of speed and distance. This all just resets if you unplug or reset the device.

// A simple LCD speedo for a cat wheel. 
// Change wheelCircumference to the distance between magnets on the wheel
// work out the total circumference of your wheel (diameter times pi) 
// then divide that by the number of magnets.

#include <Wire.h>
#include <LiquidCrystal_I2C.h>

const int reedPin = 19;                 // Pin for the magentic sensor
const float wheelCircumference = 0.84823;  // Distance between magnets in metres 
volatile int cnt = 0;     // Counts the number of wheel rotations
volatile unsigned long revolutions = 0;
unsigned long lastTime = 0;  // Time to calculate speed
float distance = 0.0;        // Total distance covered
float speed = 0.0;           // Speed in meters per second

volatile unsigned long lastTriggerTime = 0;  // Last interrupt trigger time
const unsigned long debounceDelay = 150;     // Debounce delay in milliseconds

#define I2C_SCL 22
#define I2C_SDA 21

//set up LCD display
LiquidCrystal_I2C lcd(0x27, 16, 2);  // set the LCD address to 0x27 for a 16 chars and 2 line display

void count() {
  unsigned long currentTime = millis();
  
  if (currentTime - lastTriggerTime > debounceDelay) {
    cnt++;
    revolutions++;                  // Increment the total revolutions count
    lastTriggerTime = currentTime;  // Update the last trigger time
  }
}

void setup() {
  Serial.begin(115200);
  Wire.begin(I2C_SDA, I2C_SCL);

  pinMode(reedPin, INPUT_PULLUP);                                   // Set reed pin as input
  attachInterrupt(digitalPinToInterrupt(reedPin), count, FALLING);  // Trigger count on rising edge
  lcd.init();                                                       // initialise the LCD
  lcd.backlight();                                                  // Light the beacons
}


void loop() {
  // Every 5 seconds, calculate the speed and distance
  unsigned long currentTime = millis();

  if (currentTime - lastTime >= 5000) {
    float timeInSeconds = (currentTime - lastTime) / 1000.0;

    // Calculate speed in meters per second
    speed = (cnt * wheelCircumference) / timeInSeconds;

    // Calculate total distance
    distance += cnt * wheelCircumference;

    // Go to the Write subroutine to output the results
    Write();

    // Reset the count and update the last time
    cnt = 0;
    lastTime = currentTime;
  }
}


//Writes the results to the serial monitor and the LCD
void Write() {

  // information for Serial Monitor
  Serial.println("Data:");
  Serial.print("Revolutions: ");
  Serial.println(revolutions);
  Serial.print("cnt: ");
  Serial.println(cnt);
  Serial.print("Current Speed: ");
  Serial.println(speed);
  Serial.print("Distance: ");
  Serial.println(distance);
  Serial.println(" -- ");
  // print to LCD
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Speed: ");
  lcd.print(speed);
  lcd.print("m/s");
  lcd.setCursor(0, 1);
  lcd.print("Dist: ");
  lcd.print(distance);
  lcd.print("m");
  //lcd.print("count: ");
  //lcd.print(revolutions);
}

 

Training the cat

You’re on your own with this one.. I found this video really helpful; as with all things it takes regular perseverence. Mine got the hang of it after a fortnight but it can take up to 6 weeks of coaxing.

Updated: