Skip to content

FINAL PROJECT

FITNESSCAT

  • short description

SLIDE & PRESENTATION

presentation.png

••••••••••••••••••••••••••••••

FINAL PRODUCTS

❌CAT’S PAW BUTTON

  • real photos & videos, model, section, explode, stl/fusion viewer

FILES

❌REWARD MACHINE

  • real photos & videos, model, section, explode, stl/fusion viewer

FILES

❌TREADMILL

  • real photos & videos, model, section, explode, stl/fusion viewer

FILES

••••••••••••••••••••••••••••••

FINAL PROGRAMMING

CAT’S PAW BUTTON

PUSH BUTTON + LED + NEOPIXEL + EXTERNAL BUTTON

push and external buttons activate the debug led and neopixel 33 leds strip (all yellow)

platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

lib_deps = Adafruit NeoPixel

main.cpp

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>

// Pin definitions
#define BUTTON1_PIN D7        // Internal button
#define BUTTON2_PIN D8        // External button
#define LED_PIN D2            // Debug LED
#define NEOPIXEL_PIN D1       // NeoPixel data line
#define NUM_PIXELS 33         // Number of LEDs in the NeoPixel strip

// Create NeoPixel object
Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  // Set buttons as input
  pinMode(BUTTON1_PIN, INPUT);
  pinMode(BUTTON2_PIN, INPUT);

  // Debug LED as output
  pinMode(LED_PIN, OUTPUT);

  // Initialize the NeoPixel strip
  strip.begin();
  strip.show();  // Turn all LEDs off
}

void loop() {
  // Read button states
  int button1State = digitalRead(BUTTON1_PIN);
  int button2State = digitalRead(BUTTON2_PIN);

  // Check if either button is pressed (LOW = pressed, due to pull-up logic)
  bool pressed = (button1State == LOW || button2State == LOW);

  // Turn debug LED on or off
  digitalWrite(LED_PIN, pressed ? HIGH : LOW);

  // If pressed, turn NeoPixels yellow; else, turn them off
  if (pressed) {
    for (int i = 0; i < NUM_PIXELS; i++) {
      strip.setPixelColor(i, strip.Color(255, 255, 0));  // Yellow
    }
  } else {
    for (int i = 0; i < NUM_PIXELS; i++) {
      strip.setPixelColor(i, 0);  // Off
    }
  }

  strip.show();  // Update the strip with new values

  delay(10);  // Small delay to stabilize loop
}

REWARD MACHINE

PUSH BUTTON + LED + NEOPIXEL + BUZZER + STEPPER MOTOR

push and external buttons activate the debug led and neopixel 15 leds strip together with sound, when the last led go on, the stepper motor rotates 1 rotation to the right

platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

lib_deps = AccelStepper, Adafruit NeoPixel

main.cpp

#include <Adafruit_NeoPixel.h>
#include <AccelStepper.h>

// Define pins
#define BUTTON_PIN D7       // Push button, using internal pull-up resistor
#define LED_PIN D0          // Debugging LED
#define SPEAKER_PIN D9      // Buzzer

// LED strip pins & LED number
#define LED_STRIP_PIN D10   // Neopixel LED strip
#define NUM_LEDS 14         // Total LEDs

// Stepper motor pins (ULN2003 with 28BYJ-48)
#define IN1 D5
#define IN2 D6
#define IN3 D2
#define IN4 D1

// NeoPixel setup
Adafruit_NeoPixel strip(NUM_LEDS, LED_STRIP_PIN, NEO_GRB + NEO_KHZ800);

// Stepper setup
AccelStepper stepper(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4);

// Forward declarations
void lightUpStripWithSound();
void rotateStepper();

void setup() {
    Serial.begin(115200);
    delay(500); // Give time for Serial to connect

    Serial.println("=== Setup started ===");

    pinMode(LED_PIN, OUTPUT);
    pinMode(SPEAKER_PIN, OUTPUT);
    pinMode(BUTTON_PIN, INPUT_PULLUP); // Use internal pull-up

    strip.begin();
    strip.show();

    stepper.setMaxSpeed(1000.0);
    stepper.setAcceleration(800.0);

    Serial.println("=== Setup complete ===");
}

void loop() {
    if (digitalRead(BUTTON_PIN) == LOW) {
        Serial.println("=== Button pressed! Activating sequence ===");

        digitalWrite(LED_PIN, HIGH);
        lightUpStripWithSound();
        strip.clear();
        strip.show();

        rotateStepper();

        digitalWrite(LED_PIN, LOW);
        Serial.println("=== Sequence complete ===\n");

        // Debounce delay
        delay(500);
    }
}

void lightUpStripWithSound() {
    Serial.println("[LIGHT + SOUND] Sequence started");

    for (int i = NUM_LEDS - 1; i >= 0; i--) {
        if (i > 0) {
            strip.setPixelColor(i, strip.Color(0, 0, 255)); // Blue
        } else {
            strip.setPixelColor(i, strip.Color(255, 0, 0)); // Red
        }

        int freq = map(i, 0, NUM_LEDS - 1, 2000, 500); // Reverse frequency
        tone(SPEAKER_PIN, freq, 100);
        strip.show();
        Serial.printf("[LED] LED #%d ON | [Tone] %d Hz\n", i + 1, freq);
        delay(100);
    }

    tone(SPEAKER_PIN, 2500, 300);
    delay(300);
    noTone(SPEAKER_PIN);

    Serial.println("[LIGHT + SOUND] Sequence complete");
}

void rotateStepper() {
    int stepsPerRevolution = 2048;

    Serial.println("[STEPPER] Rotating 1 full turn CW");

    stepper.move(stepsPerRevolution); // 1 full turn CW
    while (stepper.distanceToGo() != 0) {
        stepper.run();
    }

    Serial.println("[STEPPER] Rotation complete");
}

⚠️TREADMILL

PUSH BUTTON + LED + HALL SENSOR

platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

main.cpp

#include <Arduino.h>

#define HALL_SENSOR_PIN D6
#define LED_PIN D7
#define BUTTON_PIN D9

void setup() {
    pinMode(HALL_SENSOR_PIN, INPUT);
    pinMode(BUTTON_PIN, INPUT);
    pinMode(LED_PIN, OUTPUT);

    Serial.begin(115200);  // Start Serial Monitor
}

void loop() {
    int sensorState = digitalRead(HALL_SENSOR_PIN);  // Read Hall sensor
    int buttonState = digitalRead(BUTTON_PIN);      // Read button state

    if (sensorState == LOW || buttonState == LOW) {  
        digitalWrite(LED_PIN, HIGH);  // Turn LED ON
        Serial.println("LED ON");
    } else {
        digitalWrite(LED_PIN, LOW);   // Turn LED OFF
        Serial.println("LED OFF");
    }

    // Print sensor and button states for debugging
    Serial.print("Hall Sensor: ");
    Serial.println(sensorState == LOW ? "ON" : "OFF");

    Serial.print("Button: ");
    Serial.println(buttonState == LOW ? "PRESSED" : "NOT PRESSED");

    delay(300);  // Small delay to avoid spam in Serial Monitor
}

just counting the signals

#include <Arduino.h>

#define HALL_SENSOR_PIN D6

unsigned long impulseCount = 0;
int lastSensorState = HIGH;

void setup() {
    pinMode(HALL_SENSOR_PIN, INPUT);
    Serial.begin(115200);
}

void loop() {
    int sensorState = digitalRead(HALL_SENSOR_PIN);

    // Count falling edge (HIGH → LOW)
    if (lastSensorState == HIGH && sensorState == LOW) {
        impulseCount++;
        Serial.print("Impulse Count: ");
        Serial.println(impulseCount);
    }

    lastSensorState = sensorState;

    delay(10);  // Small delay to avoid double-counting
}

••••••••••••••••••••••••••••••

FINAL NETWORK & COMMUNICATION SYSTEM

I2C (CAT’S PAW BUTTON & REWARD MACHINE)

CAT’S PAW BUTTON - SENDER

platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

lib_deps = Adafruit NeoPixel

main.cpp

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>
#include <Wire.h>

// Pin definitions
#define BUTTON1_PIN D7        // Internal button
#define BUTTON2_PIN D8        // External button
#define LED_PIN D2            // Debug LED
#define NEOPIXEL_PIN D1       // NeoPixel data line
#define NUM_PIXELS 33         // Number of LEDs in the NeoPixel strip

// I2C
#define I2C_ADDRESS 0x08      // Address of the reward machine

Adafruit_NeoPixel strip(NUM_PIXELS, NEOPIXEL_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pinMode(BUTTON1_PIN, INPUT);
  pinMode(BUTTON2_PIN, INPUT);
  pinMode(LED_PIN, OUTPUT);

  strip.begin();
  strip.show();

  Wire.begin();  // Start as I2C master
}

void loop() {
  int button1State = digitalRead(BUTTON1_PIN);
  int button2State = digitalRead(BUTTON2_PIN);
  bool pressed = (button1State == LOW || button2State == LOW);

  digitalWrite(LED_PIN, pressed ? HIGH : LOW);

  static bool sent = false;

  if (pressed) {
    // Turn on NeoPixels (Yellow)
    for (int i = 0; i < NUM_PIXELS; i++) {
      strip.setPixelColor(i, strip.Color(255, 255, 0));
    }

    if (!sent) {
      Wire.beginTransmission(I2C_ADDRESS);
      Wire.write('G');  // Send signal
      Wire.endTransmission();
      sent = true;
    }
  } else {
    for (int i = 0; i < NUM_PIXELS; i++) {
      strip.setPixelColor(i, 0);  // Off
    }
    sent = false;
  }

  strip.show();
  delay(10);
}

REWARD MACHINE - RECEIVER

platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

lib_deps = AccelStepper, Adafruit NeoPixel

main.cpp

#include <Adafruit_NeoPixel.h>
#include <AccelStepper.h>
#include <Wire.h>

// Pins
#define BUTTON_PIN D7
#define LED_PIN D0
#define SPEAKER_PIN D9
#define LED_STRIP_PIN D10
#define NUM_LEDS 5

// Stepper motor pins (ULN2003 + 28BYJ-48)
#define IN1 D5
#define IN2 D6
#define IN3 D2
#define IN4 D1

Adafruit_NeoPixel strip(NUM_LEDS, LED_STRIP_PIN, NEO_GRB + NEO_KHZ800);
AccelStepper stepper(AccelStepper::HALF4WIRE, IN1, IN3, IN2, IN4);

volatile bool triggerReward = false;

void setup() {
  Serial.begin(115200);
  delay(500);
  Serial.println("=== Setup started ===");

  pinMode(LED_PIN, OUTPUT);
  pinMode(SPEAKER_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);

  strip.begin();
  strip.show();

  stepper.setMaxSpeed(1000.0);
  stepper.setAcceleration(800.0);

  Wire.begin(0x08);  // Start as I2C slave
  Wire.onReceive(receiveEvent);

  Serial.println("=== Setup complete ===");
}

void loop() {
  if (triggerReward || digitalRead(BUTTON_PIN) == LOW) {
    triggerReward = false;

    Serial.println("=== Reward Triggered ===");
    digitalWrite(LED_PIN, HIGH);

    lightUpStripWithSound();
    strip.clear();
    strip.show();

    rotateStepper();
    digitalWrite(LED_PIN, LOW);

    Serial.println("=== Sequence complete ===\n");
    delay(500);  // Debounce
  }
}

void receiveEvent(int howMany) {
  while (Wire.available()) {
    char c = Wire.read();
    if (c == 'G') {
      triggerReward = true;
    }
  }
}

void lightUpStripWithSound() {
  Serial.println("[LIGHT + SOUND] Starting");

  for (int i = NUM_LEDS - 1; i >= 0; i--) {
    if (i > 0) {
      strip.setPixelColor(i, strip.Color(0, 0, 255)); // Blue
    } else {
      strip.setPixelColor(i, strip.Color(255, 0, 0)); // Red
    }

    int freq = map(i, 0, NUM_LEDS - 1, 2000, 500);
    tone(SPEAKER_PIN, freq, 100);
    strip.show();
    delay(100);
  }

  tone(SPEAKER_PIN, 2500, 300);
  delay(300);
  noTone(SPEAKER_PIN);

  Serial.println("[LIGHT + SOUND] Done");
}

void rotateStepper() {
  int stepsPerRevolution = 2048;

  Serial.println("[STEPPER] Rotating...");
  stepper.move(-stepsPerRevolution);
  while (stepper.distanceToGo() != 0) {
    stepper.run();
  }
  Serial.println("[STEPPER] Done");
}

WIFI

platformio.ini

; PlatformIO Project Configuration File for ESP32C3 Async Web Server Button Example
;
; This file configures how PlatformIO builds and uploads the project to the ESP32C3
; It includes special settings for ESP32C3 compatibility with the AsyncWebServer library
;
; For more options and documentation, see:
; https://docs.platformio.org/page/projectconf.html

[env:seeed_xiao_esp32c3]
; Target platform - Espressif32 covers all ESP32 variants including ESP32C3
platform = espressif32

; Specific board definition - Seeed Studio XIAO ESP32C3
board = seeed_xiao_esp32c3

; Framework - Arduino compatibility layer for ESP32
framework = arduino

; Serial monitor baud rate - must match the Serial.begin() rate in the code
monitor_speed = 115200

; Library dependencies - these are automatically downloaded and installed
lib_deps = 
    ; AsyncWebServer library - handles HTTP requests asynchronously
    https://github.com/me-no-dev/ESPAsyncWebServer.git
    ; AsyncTCP library - required dependency for AsyncWebServer
    https://github.com/me-no-dev/AsyncTCP.git

; Build flags - compiler directives to fix ESP32C3 compatibility issues
build_flags = 
    ; Run AsyncTCP on core 0 (ESP32C3 is single core, but this is needed for compatibility)
    -DCONFIG_ASYNC_TCP_RUNNING_CORE=0
    ; Disable watchdog timer for AsyncTCP to prevent crashes on ESP32C3
    -DCONFIG_ASYNC_TCP_USE_WDT=0

header.h

/*
Header file for ESP32C3 Async Web Server Button Example
This file contains all the necessary library includes for the project
 */

// Core Arduino functionality for ESP32
#include <Arduino.h>

// WiFi library for setting up Access Point
#include "WiFi.h"

// Asynchronous Web Server library for handling HTTP requests without blocking the main loop
#include "ESPAsyncWebServer.h"

main.cpp

// Include the header file where required libraries are defined
#include "header.h"

// Define the pin number for the push button
// D7 on the XIAO ESP32-C3 board is GPIO6 internally
#define buttonPin D7

// This string holds the current state of the system ("on" or "off")
String state = "off";

// These variables track whether the button is pressed or not
bool lastButtonState = false;  // What the button state was during the last check
bool currentButtonState = false;  // What the button state is right now

// This counter tracks how many HTTP requests were received
int requestCount = 0;

// These are the WiFi network name (SSID) and password
// This is the Access Point that the ESP32 will create
const char *ssid = "catz";
const char *password = "miaumiau01";

// Create a web server object that listens on port 80 (the standard for HTTP)
AsyncWebServer server(80);

/**
 * This function returns the current ON/OFF state as a string
 * It also logs each request to the Serial Monitor
 */
String getState() {
  requestCount++;  // Increase the counter by one
  Serial.printf("Request #%d received. Current state: %s\n", requestCount, state.c_str());
  return state;  // Return "on" or "off"
}

/**
 * This function checks the button to see if it's pressed or released
 * If it's pressed, it toggles the state between "on" and "off"
 */
void checkButtonAndToggleState() {
  // Read the current button state: HIGH if pressed, LOW if not
  currentButtonState = digitalRead(buttonPin);

  // Detect a rising edge: button was not pressed before, but is now
  if (currentButtonState && !lastButtonState) {
    Serial.println("Button PRESSED!");

    // Toggle the state: switch from "off" to "on" or from "on" to "off"
    state = (state == "off") ? "on" : "off";
    Serial.println("State changed to: " + state);
  }
  // Detect falling edge: button was pressed, now it's released
  else if (!currentButtonState && lastButtonState) {
    Serial.println("Button RELEASED!");
  }

  // Save the current state for the next check
  lastButtonState = currentButtonState;
}

/**
 * This function runs once when the ESP32 starts up
 * It sets up the WiFi network, web server, and serial communication
 */
void setup() {
  delay(1000);  // Give the board a moment to power up fully

  Serial.begin(115200);  // Start communication with the computer
  while (!Serial) {
    ; // Wait here until the Serial Monitor is ready
  }

  delay(1000);  // Wait another second to make sure Serial Monitor catches everything

  Serial.println("\n=== ESP32C3 Button Toggle Web Server Starting ===");

  // Set the button pin as input so we can read from it
  pinMode(buttonPin, INPUT);
  Serial.println("Button pin initialized");

  // Start WiFi in Access Point mode so other devices can connect to it directly
  WiFi.softAP(ssid, password);
  IPAddress IP = WiFi.softAPIP();  // Get the IP address assigned to the ESP32

  Serial.println("Access Point started");
  Serial.println("Access Point IP: " + IP.toString());

  // Define what to do when someone accesses http://<IP>/onoff
  server.on("/onoff", HTTP_GET, [](AsyncWebServerRequest *request) {
    request->send(200, "text/plain", getState());  // Send the current "on" or "off" state
  });

  // Start the web server
  server.begin();
  Serial.println("Server ready at: http://" + IP.toString() + "/onoff");
  Serial.println("Initial state: " + state);
  Serial.println("Press the button to toggle state");
  Serial.println("=================================");
}

/**
 * This function runs over and over, as long as the ESP32 is powered
 * It checks the button and prints a heartbeat message every 5 seconds
 */
void loop() {
  checkButtonAndToggleState();  // Look for button presses and change state if needed

  // This block prints a message every 5 seconds to show the board is alive
  static unsigned long lastHeartbeat = 0;
  if (millis() - lastHeartbeat > 5000) {
    Serial.println("Heartbeat - ESP32 is running");
    lastHeartbeat = millis();  // Save the time of this heartbeat
  }

  delay(50);  // Short pause to reduce CPU usage and debounce the button
}

NODE RED

  • show the process

••••••••••••••••••••••••••••••

FINAL PCB’s

⚠️CAT’S PAW BUTTON

kicadbutton.jpg

kicadbutton2.jpg

pcbbuttonfinal.jpg

pcbbuttonfinal2.jpg

FILES

⚠️REWARD MACHINE

  • produce finalPCB

FILES

⚠️TREADMILL

  • produce final PCB or keep the old one

FILES

••••••••••••••••••••••••••••••

17. INVENTION & INTELECTUAL PROPERTY (WEEK19)

❌REWARD MACHINE PROTOTYPE UPDATE

  • finish 3D model 3D & start prototyping process

❌TREADMILL PROTOTYPE UPDATE

  • new side panels
  • mechanical system for the hall sensor
  • PCB box holder with mechanic part

16. PROJECT DEVELOPMENT UPDATE (WEEK18)

CAT’S PAW BUTTON

Final 3D model, wit a new bottom lid, and updated base structure with a bigger slot for the cable and PCB board

buttonfinalmodel.jpg

buttonfinalmodel2.jpg

buttonfinalmodel3.jpg

Wiring, PCB mount and button mechanic system

buttonfinalprototype.jpg

There was lots of prototyping of the button itself, that was keep braking. Here’s the final section through the button system

buttonfinalmodel4.jpg

Final prints:

buttonfinalprototype2.jpg

Bottom lid has some ant slippery pads

FINAL PRODUCT

15. SYSTEM INTEGRATION (WEEK16)

CAT’S PAW BUTTON FINAL PCB

kicadbutton.jpg

kicadbutton2.jpg

pcbbuttonfinal.jpg

pcbbuttonfinal2.jpg

CAT’S PAW BUTTON 3D MODEL & PROTOTYPING UPDATE

buttonmodel.jpg

buttonmodel2.jpg

buttonmodel3.jpg

buttoncable.jpg

TREADMIL 3D MODEL & PROTOTYPING UPDATE

treadmillmodel.jpg

treadmillmodel2.jpg

treadmillmodel3.jpg

rodes.jpg

crosses.jpg

nt2.jpg

14. INTERFACE AND APLICATION PROGRAMMING (WEEK15)

TREADMILL PCB + PROCESSING CODE

PROCESSING CODE

import processing.serial.*;  // Import the Serial library to communicate with Arduino

Serial myPort;               // Declare a Serial object for communication
PImage img;                  // Declare an image object to hold the .png image
float angle = 0;             // Angle of rotation for the image
boolean rotateImage = false; // Boolean flag to track whether image should rotate

void setup() {
  size(800, 800);            // Set the size of the display window to 800x800 pixels
  img = loadImage("cat.png"); // Load the image file (must be in sketch folder)
  img.resize(200, 200);       // Resize the image to 200x200 pixels

  println(Serial.list());     // Print a list of available serial ports to the console

  // Choose the correct serial port from the list (index [2] in this case)
  // This should match the port your Arduino/XIAO is connected to
  String portName = Serial.list()[2];
  myPort = new Serial(this, portName, 115200); // Open the serial port at 115200 baud
  myPort.bufferUntil('\n');   // Tell Processing to read incoming data until it sees a newline '\n'
}

void draw() {
  background(255);           // Clear the background with white color every frame

  pushMatrix();              // Save the current transformation matrix
  translate(width/2, height/2); // Move the origin (0,0) to the center of the screen

  if (rotateImage) {         // If the flag is true (sensor/button is active)
    angle += 0.05;           // Increase the rotation angle a bit (controls speed of rotation)
  }

  rotate(angle);             // Apply the rotation transformation
  imageMode(CENTER);         // Draw the image centered on the origin
  image(img, 0, 0);          // Draw the image at the rotated origin (screen center)
  popMatrix();               // Restore the original transformation matrix
}

void serialEvent(Serial myPort) {
  // This function is automatically called when new serial data is available

  String inData = trim(myPort.readStringUntil('\n')); // Read incoming serial data up to newline and trim spaces
  println("Received: " + inData);                     // Print the received message to the console

  // If Arduino sent "LED ON", start rotating the image
  if (inData.equals("LED ON")) {
    rotateImage = true;
  }
  // If Arduino sent "LED OFF", stop rotating the image
  else if (inData.equals("LED OFF")) {
    rotateImage = false;
  }
}

13. MOLDIG & CASTING (WEEK14)

CAT’S PAW BUTTON 3D MODEL UPDATE & SILICON CAST

buttonmold.jpg

buttonmold2.jpg

12. MACHINE BUILDING (WEEK12/13)

****I build a plotter that could be an extension to my FinessCat

idea.jpg

11. NETWORKING & COMMUNICATION (WEEK11)

SYSTEM SCHEMATIC

diagram.jpg

CAT’S PAW BUTTON NEW PCB BOARD

Unfortunately I overlooked a mistake I did in THE REWARD MACHINE’s PCB and I repeated it:

xiao.jpg

kikadwrong0.jpg

kikadwrong.jpg

I spend 2 days looking for the problem in the code and in the program (because the wiring and paths were showing good connection, but between wrong pins on two boards)

I struggled with the production software and my PCB came out with very thin paths, since I couldn’t put the offset OUT.

buttonpcbthiny.jpg

But I mange to solder everything.

Some of the paths unglued from the board, because they were just a little bit thicker then hair (on the photo they look nicer then in reality) and the pin header didn’t have enough surface to hold onto so while I was detaching the Xiao to make some jumping wires connection because of the wrong pinouts- it pulled everything out

pinheaderbroken.jpg

But I need to make a new PCB anyways…

I2C CONNECTION BETWEEN THE CAT’S PAW BUTTON & THE REWARD MACHINE CODES

Since I destroyed THE CAT’S PAW BUTTON PCB I used a breadboard, and THE REWARD MACHINE PCB had possibility to use I2C connection because on my pin D6 I also had a header.

I switched to VISUAL STUDIO CODE & PlatformIO platformio.ini

[env:seeed_xiao_esp32c3]
platform = espressif32
board = seeed_xiao_esp32c3
framework = arduino
monitor_speed = 115200
upload_speed = 460800

REWARD MACHINE square board - MAIN BOARD

main.cpp

#include <Arduino.h>
#include <Wire.h>

const int button = D7;   // Button pin
const int led = D2;      // Local LED (optional)
const int squareAddress = 0x08; // I2C address of Square

bool lastButtonState = HIGH;  // Previous state (starts HIGH = not pressed)

void setup() {
  delay(1000);
  Serial.begin(115200);

  pinMode(button, INPUT);     // External pull-up
  pinMode(led, OUTPUT);       // Optional local LED

  Wire.begin();               // Start I2C as master

  Serial.println("Circle ready (I2C master)");
}

void loop() {
  bool currentState = digitalRead(button);

  // Only act on state change
  if (currentState != lastButtonState) {
    lastButtonState = currentState;

    // Invert to get "pressed = LOW"
    bool pressed = (currentState == LOW);

    digitalWrite(led, pressed);  // Optional: reflect button locally

    Wire.beginTransmission(squareAddress); // Begin I2C transmission
    Wire.write(pressed ? '1' : '0');       // Send '1' or '0'
    Wire.endTransmission();                // End transmission

    Serial.println(pressed ? "Button pressed → LED ON" : "Button released → LED OFF");
  }

  delay(50); // Small debounce delay
}

CAT’S PAW BUTTON circle board SECONDARY BOARD

  • changed for the a breadboard -

main.cpp

#include <Arduino.h>
#include <Wire.h>

const int led = D7;    // LED pin
const int i2cAddress = 0x08;  // I2C address of this board

// Function to handle incoming I2C data
void receiveEvent(int numBytes) {
  if (numBytes > 0) {
    char command = Wire.read(); // Read the first byte
    if (command == '1') {
      digitalWrite(led, HIGH);   // Turn LED on
      Serial.println("LED ON (received 1)");
    } else if (command == '0') {
      digitalWrite(led, LOW);    // Turn LED off
      Serial.println("LED OFF (received 0)");
    }
  }
}

void setup() {
  delay(1000);
  Serial.begin(115200);

  pinMode(led, OUTPUT);  // Set LED as output
  digitalWrite(led, LOW); // Ensure LED is off initially

  Wire.begin(i2cAddress);      // Start I2C as slave
  Wire.onReceive(receiveEvent); // Register receive event handler

  Serial.println("Square ready (I2C slave)");
}

void loop() {
  // Nothing needed here — everything happens on receiveEvent
}

10. OUTPUT DEVICES (WEEK10)

REWARD MACHINE CODE

After pressing the button:

  • the debug led should go ON only when the button is pressed, and OFF when the button is realised
  • the led strip increasing in blue colour from led 1 -37 and last one 38th is red
  • the buzzer increases the sound together with the led strip, makes a stronge sound on the last, 38th red led
  • the servo motor rotates after led strip sequence is finished

PUSH BUTTON + LED + SERVO + NEOLED (ARDUINO IDE)

#include <Adafruit_NeoPixel.h>
#include <ESP32Servo.h>

#define BUTTON_PIN D7  // Button with pull-up resistor on D7
#define LED_PIN D2      // Debugging LED on D2
#define LED_STRIP_PIN D9 // Neopixel LED strip on D9
#define SPEAKER_PIN D6  // Buzzer on D6
#define SERVO_PIN D8    // Servo motor on D8
#define NUM_LEDS 38    // Total LEDs in strip

Adafruit_NeoPixel strip(NUM_LEDS, LED_STRIP_PIN, NEO_GRB + NEO_KHZ800);
Servo servoMotor;

void setup() {
    pinMode(BUTTON_PIN, INPUT);  // Set button pin as input
    pinMode(LED_PIN, OUTPUT);     // Set LED pin as output
    pinMode(SPEAKER_PIN, OUTPUT); // Set buzzer pin as output
    strip.begin();
    strip.show(); // Initialize all pixels to 'off'
    servoMotor.attach(SERVO_PIN);
    servoMotor.write(90); // Stop position for continuous servo
}

void loop() {
    int buttonState = digitalRead(BUTTON_PIN); // Read button state

    if (buttonState == LOW) { // Button is active low due to pull-up resistor
        digitalWrite(LED_PIN, HIGH); // Turn on debug LED when button is pressed
        lightUpStripWithSound();
        strip.clear();  // Turn off all LEDs after animation
        strip.show();
        rotateServo();  // Start servo movement after LED sequence finishes
    } else {
        digitalWrite(LED_PIN, LOW);  // Turn off debug LED when button is released
    }
}

void lightUpStripWithSound() {
    for (int i = 0; i < NUM_LEDS; i++) {
        if (i < NUM_LEDS - 1) {
            strip.setPixelColor(i, strip.Color(0, 0, 255)); // LED blue for 1 to 37
        } else {
            strip.setPixelColor(i, strip.Color(255, 0, 0)); // Last LED (38) red
        }

        tone(SPEAKER_PIN, map(i, 0, NUM_LEDS - 1, 500, 2000), 100); // Increasing "ping"
        strip.show();
        delay(100); // Delay for effect
    }
    tone(SPEAKER_PIN, 2500, 300); // Final stronger ping
    delay(300);
    noTone(SPEAKER_PIN); // Stop buzzer
}

void rotateServo() {
    for (int i = 0; i < 2; i++) { // Move 0 → 180 → 0 two times
        servoMotor.write(180);
        delay(500); // Allow time to reach position
        servoMotor.write(0);
        delay(500); // Allow time to return
    }
}

Unfortunately I’m not able to write the code correctly. The debug led is ON during the whole time function is running (when the button is not pressed anymore) and the servo is going crazy.

I’m not understanding the problem because the code below works fine

SERVO MOVES FROM 0 → 180 → 0 TWICE AFTER PRESSING THE BUTTON (ARDUINO IDE)

#include <ESP32Servo.h>

#define BUTTON_PIN D7  // Button with pull-up resistor on D7
#define SERVO_PIN D8   // Servo motor on D8

Servo servoMotor;
bool buttonPressed = false;

void setup() {
    pinMode(BUTTON_PIN, INPUT);  // Set button pin
    servoMotor.attach(SERVO_PIN);
    servoMotor.write(0); // Start at 0 degrees
}

void loop() {
    if (digitalRead(BUTTON_PIN) == LOW) { // Button pressed
        if (!buttonPressed) { // Only trigger once per press
            buttonPressed = true;
            rotateServoTwice();
        }
    } else {
        buttonPressed = false; // Reset when button is released
    }
}

void rotateServoTwice() {
    for (int i = 0; i < 2; i++) { // Move 0 → 180 → 0 two times
        servoMotor.write(180);
        delay(500); // Allow time to reach position
        servoMotor.write(0);
        delay(500); // Allow time to return
    }
}

REWARD MACHINE SCHEMATICS AND PCB BOARD

kikadreward.jpg

kikadreward2.jpg

pcb.jpg

pcbBack.jpg

OUTPUD DEVICES CODES (TESTED WITH TREADMILL PCB)

SERVO MOTOR CODE (ARDUINO IDE)

#include <ESP32Servo.h>

#define LED_PIN D7
#define BUTTON_PIN D9

Servo myservo;  // create Servo object to control a servo

void setup() {
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  myservo.attach(D6);  // attaches the servo on pin D6 to the Servo object
}

void loop() {
  if (digitalRead(BUTTON_PIN) == LOW) {  // Check if button is pressed
    for (int i = 0; i < 3; i++) {  // Repeat the cycle 3 times
      digitalWrite(LED_PIN, HIGH);  // Turn LED on
      myservo.write(0);  
      delay(300);

      myservo.write(180);
      delay(300);
    }
    digitalWrite(LED_PIN, LOW);  // Turn LED off
  }
}

NEO PIXEL LED STRIP WS2812B CODE (ARDUINO IDE)

#include <Adafruit_NeoPixel.h>

#define LED_PIN D6
#define NUM_LEDS 38  // Number of LEDs in the strip

Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, LED_PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  strip.begin();
  strip.show();  // Initialize all pixels to 'off'
}

void loop() {
  rainbowEffect();  // Always run the rainbow effect
}

void rainbowEffect() {
  uint16_t i, j;
  for (j = 0; j < 256; j++) {
    for (i = 0; i < strip.numPixels(); i++) {
      strip.setPixelColor(i, Wheel((i + j) & 255));
    }
    strip.show();
    delay(20);
  }
}

uint32_t Wheel(byte WheelPos) {
  WheelPos = 255 - WheelPos;
  if (WheelPos < 85) {
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  }
  if (WheelPos < 170) {
    WheelPos -= 85;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
  WheelPos -= 170;
  return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}

9. INPUT DEVICES (WEEK09)

TREADMILL SCHEMATICS AND PCB BOARD

kikad0.jpg

kikad.jpg

pcb.jpg

TREADMILL CODE

HALL SENSOR + LED (ARDUINO IDE)

#define HALL_SENSOR_PIN D6  // White wire (signal) from the Hall sensor
#define LED_PIN D7          // LED connected to D7

void setup() {
    pinMode(HALL_SENSOR_PIN, INPUT);
    pinMode(LED_PIN, OUTPUT);
}

void loop() {
    int sensorState = digitalRead(HALL_SENSOR_PIN);  // Read Hall sensor

    if (sensorState == LOW) {
        digitalWrite(LED_PIN, HIGH);  // Turn ON LED when magnet is detected
    } else {
        digitalWrite(LED_PIN, LOW);   // Turn OFF LED when no magnet
    }

    delay(50);  // Small delay for stability
}


HALL SENSOR + LED + PUSH BUTTON TOGGLE (ARDUINO IDE)

#define HALL_SENSOR_PIN D6
#define LED_PIN D7
#define BUTTON_PIN D9

void setup() {
    pinMode(HALL_SENSOR_PIN, INPUT);
    pinMode(BUTTON_PIN, INPUT);
    pinMode(LED_PIN, OUTPUT);

    Serial.begin(115200);  // Start Serial Monitor
}

void loop() {
    int sensorState = digitalRead(HALL_SENSOR_PIN);  // Read Hall sensor
    int buttonState = digitalRead(BUTTON_PIN);      // Read button state

    if (sensorState == LOW || buttonState == LOW) {  
        digitalWrite(LED_PIN, HIGH);  // Turn LED ON
        Serial.println("LED ON");
    } else {
        digitalWrite(LED_PIN, LOW);   // Turn LED OFF
        Serial.println("LED OFF");
    }

    // Print sensor and button states for debugging
    Serial.print("Hall Sensor: ");
    Serial.println(sensorState == LOW ? "ON" : "OFF");

    Serial.print("Button: ");
    Serial.println(buttonState == LOW ? "PRESSED" : "NOT PRESSED");

    delay(300);  // Small delay to avoid spam in Serial Monitor
}

8. PCD PRODUCTION (WEEK08)

CAT’S PAW BUTTON & REWARD MACHINE PCB BOARDS

soldering3.jpg

CAT’S PAW BUTTON CODE

I made again same circuit in Wokwi and edited my code to show ON and OF in the serial monitor

WOKWI.jpg

PUSH BUTTON (pull up resistor) + LED (ARDUINO IDE)

// Define pin numbers for button and LED
const int button = D9;   // Button pin (input)
const int led = D7;      // LED pin (output)

void setup() {
  // Start the serial communication at 115200 baud rate
  Serial.begin(115200);  

  // Set the button pin as input (with external pull-up resistor)
  pinMode(button, INPUT);  

  // Set the LED pin as output to control the LED state
  pinMode(led, OUTPUT);    
}

void loop() {
  // Read the button state (HIGH = not pressed, LOW = pressed)
  bool buttonState = digitalRead(button);  

  // Invert the button state to control the LED (LED on when button is pressed)
  digitalWrite(led, !buttonState);  

  // Print the current state of the LED to the serial monitor (ON or OFF)
  Serial.println(buttonState ? "OFF" : "ON");  

  // Small delay to debounce the button and avoid multiple reads in quick succession
  delay(100);  
}

REWARD MACHINE CODE

I did the same for my Reward Machine PCB

WOKWI2.jpg

PUSH BUTTON (pull down resistor) + LED (ARDUINO IDE)

// Define pin numbers for button and LED
const int button = D9;   // Button pin (input)
const int led = D6;      // LED pin (output)

void setup() {
  // Start serial communication at 115200 baud rate
  Serial.begin(115200);  

  // Print "Ready" to the Serial Monitor to indicate setup is complete
  Serial.println("Ready");  

  // Set the button pin as input (with pull-down resistor)
  pinMode(button, INPUT);  

  // Set the LED pin as output to control the LED state
  pinMode(led, OUTPUT);    

  // Start with the LED turned off
  digitalWrite(led, LOW);  
}

void loop() {
  // Read the current state of the button
  // Returns HIGH when the button is pressed, LOW when it is not pressed
  bool buttonState = digitalRead(button);  

  // Set the LED state based on the button state
  // The LED will turn on when the button is pressed (HIGH), off when not pressed (LOW)
  digitalWrite(led, buttonState);          

  // Print the current state of the LED to the Serial Monitor
  // Print "ON" if the button is pressed, "OFF" if it is not
  Serial.println(buttonState ? "ON" : "OFF");  

  // Small delay to debounce the button and prevent multiple reads in quick succession
  delay(100);  
}

7. COMPUTER-CONTROLLED MACHINING (WEEK07)

TREADMILL 3D MODEL

I made my Treadmill parametric model from previous weeks more detailed.

treadmill.jpg

and created first CNC cut prototype with bearings, rods and 3D printed conical wheels

t6.jpg

t8.jpg

t9.jpg

They don’t feel stable, so I need to work more on the design…

6. ELECTRONIC DESIGNS (WEEK06)

CAT’S PAW BUTTON CIRCUIT UPDATE

My previous simulation in Wokwi had a small error:

  • Xiao ESP32-C3 pins only support 3.3V and ****using 5V could damage the microcontroller.

Here’s the corrected schematic:

kicad_button_wokwi.jpg

CAT’S PAW BUTTON SCHEMATICS & VISUALISATION

kicad_button_schemat.jpg

kicad_button_pcb.jpg

kicad_button_3d.jpg

REWARD MACHINE SCHEMATICS & VISUALISATION

Here’s the first approach to the PCB design and its visualisation (where I was expecting to see a real buzzer since in schematic I can see a speaker icon not a pin out, but maybe that’s better, I can place the speaker further from the board)

kicad_RM_schemat.jpg

kicad_RM_pcb.jpg

kicad_RM_3d.jpg

5. 3D PRINTING (WEEK05)

CAT’S PAW BUTTON 3D MODEL

I tried some new features in Fusion360 called FROM MODELING, and here’s my first look of the remote button:

FINALBUTTON.jpg

FINALBUTTON2.jpg

CAT’S PAW BUTTON PRINTS

I Printed the bottom part on the BambuLab and the middle one on the Prusa.

button_print.jpg

I tried also a resin printer with a rubber material, unfortunately I could get proper outcome:

almost5.jpg

4. EMBEDED PROGRAMMING (WEEK04)

CAT’S PAW BUTTON WOKWI SYMULATION

CREDIT: Francisco Ruz (code edited for my purposes)

CAT’S PAW BUTTON CODE

BUTTON + LED (ARDUINO IDE)

const int buttonRed = D9; // **Defines pin D9** as the button input
const int ledRed = D2;    // **Defines pin D2** as the LED output

void setup() {
Serial.begin(115200);       // Start Serial Monitor
Serial.println("Ready");    // Prints "Ready"

pinMode(buttonRed, INPUT);  //Button not pressed = pin reads HIGH (due to the pull-up resistor)

pinMode(ledRed, OUTPUT);    // Set LED pin as OUTPUT

digitalWrite(ledRed, LOW);  // Start with LED OFF
}

void loop() {
bool redState = digitalRead(buttonRed);  //reads HIGH or LOW

digitalWrite(ledRed, !redState);        //controls LED by setting it to HIGH (on) or LOW (off)

Serial.print("Red: ");
Serial.print(!redState); //Prints the LED states to the Serial Monitor

delay(100);   // **100ms delay** to debounce the button.
}

3. COMPUTER-AIDED CUTTING (WEEK03)

TREADMILL 3D MODEL

I learned more about parametric design and created fully adjustable model of my Treadmill:

PARAMETER_SKETCH.jpg

PARAMETER_SKETCH2.jpg

2. COMPUTER-AIDED DESIGN (WEEK02)

REWARD MACHINE 3D MODEL

My first attempt at using Fusion 360 and creating a 3D model of the Reward Machine.

MY_PROJECT10.jpg

1. PROJECT MANAGEMENT (WEEK01)

CONCEPT

FitnessCat is a cat gym that consists of several smart devices. The primary component is a food dispenser that provides exercise rewards (ideally low-calorie snacks). This device works wirelessly with three additional exercise machines:

  1. Wireless Button: A simple wireless button that triggers snack release when pressed. The idea is to place it far enough so the cat must travel a certain distance to earn the reward.
  2. Climbing Tower or Wall: This device recognizes when the cat climbs to the top, signalling progress with lights and sounds. Upon reaching the top, the cat is rewarded with a treat.
  3. Treadmill Wheel: Equipped with a distance sensor, this device can be programmed to specific goals (e.g., 100 meters). Like the other devices, it signals progress and dispenses a reward once the target is achieved.
  4. Future Expansions: There are possibilities to extend the system with additional types of devices.

The reward system uses signals like lights and sounds that are easily recognized by cats but not disruptive to humans, ensuring the device remains functional without being annoying.

The idea came from noticing that two of my cats clearly get far too little exercise during the day. Naturally, they tend to be more active at night, which is when humans rest. The goal is to create a smart device that encourages cats to move and burn calories at night- without disturbing their owners.

SKETCHES

FPsketch_result.jpg

FPsketch2a_result.jpg

FPsketch2b_result.jpg