Skip to content
English
Task

Kattemater

En 3D-printa kattemater med servomotor gir presis fôrdosering og er et godt eksempel på hvordan vi kan kombinere digitale verktøy og automatikk i teknologi- og produktutvikling.

Hva er en kattemater?

En automatisert kattemater er en liten maskin som sikrer regelmessig og kontrollert fôring selv om katteeieren ikke er hjemme, noe som betyr mindre stress for både eier og katt.

Når du lager en slik kattemater, lærer du samtidig om mekanisk design, elektronikk, programmering og 3D-printing. Du bruker additiv produksjon (3D-printing) og enkle, lett tilgjengelige komponenter for å utvikle et komplett produkt.

Funksjonsmåte

Matingsmekanismen aktiveres via knapp eller tidsstyring, servomotoren roterer doseringshjulet slik at neste kammer med mat kommer over utløpet, og maten faller ned i skåla.

Mengden fôr kan du programmere ved å angi antall grader servomotoren skal rotere for hver mating. Alternativt kan du måle volumet til en porsjon med fôr og deretter designe kamrene på fôringshjulet slik at de rommer riktig volum på fôr.

Hovedkomponenter

Kattemateren består av følgende hovedkomponenter:

  • Arduino Uno eller Nano – programmerbar mikrokontroller som styrer servomotoren

  • roterende servomotor (f.eks. MG996R)– roterer et doseringshjul med faste kamre

  • hus og doseringshjul – mekanisk struktur som blir tegna digitalt og 3D-printa

  • trykknapp- eller tidsstyring (valgfritt) – manuell mating eller automatisk mating i forhåndsprogrammerte faste intervaller

Produksjonsprosesser

En automatisk kattemater er et produkt som krever flere ulike produksjonsprosesser. Her følger en kort oversikt.

1. 3D-modellering og konstruksjon

  • Utforming av matehjul, kapsling, lokk og luke i et CAD-program (f.eks. Fusion 360)

  • Optimalisering av passform, bevegelse og volum

  • Eksport av -filer og utskrift på -printer (bruk av eller )

2. Elektronikk og kopling

  • Kabling av servomotor og Arduino

  • Enkel strømforsyning (batteri eller USB)

  • Eventuell tilkopling av bryter eller sensor

3. Programmering

  • Bruk av Arduino IDE

  • Rotasjon av servo ved aktivering, f.eks. 45° per mating

  • Tidsintervaller via millis()-funksjon i Arduino (ved bruk av tidsstyring)

  • Bruk av state machine-struktur hvis flere funksjoner ønskes

4. Testing og justering

  • Kalibrering av rotasjon og mating

  • Justering av hjulkamre for riktig porsjonsstørrelse

  • Kontroll av passform og klaringer

5. Dokumentasjon og refleksjon

  • Teknisk tegning og 3D-modeller

  • Kodekommentarer og koplingsskjema

  • Evaluering av funksjon, inkludert forbedringsforslag

Eksempel på produkt

Vi har laga et eksempel på en kattemater og har lagt ved tegninger og filer for utskrift på en 3D-printer. Vi har også lagt ved eksempler på kode som kan brukes til å styre kattemateren. Du kan ta utgangspunkt i eksempelmaterialet vårt og tilpasse det om du vil.

Enkelte deler er spesialdesigna til en spesiell komponent, slik som servomotoren og mikrobryterne. Hvis du benytter en servomotor eller mikrobryter med andre mål, må du justere designet på eksempelet eller lage et helt nytt design.

Eksempelproduktet har tre trykknapper. Én av knappene aktiverer mating. De to andre kan for eksempel brukes til å øke eller minske fôrmengden med en viss prosentandel for hvert trykk. Kanskje du også vil kople til et display for å få informasjon om fôringa?

Kode – ekstern spenningsforsyning med timer

#include <Servo.h>

#define second 1000

#define minute 60000

#define hour 3600000

Servo servo;

const int servoPin = 9;

const int buttonDispensePin = 7;

const int buttonMinusPin = 6;

const int buttonPlusPin = 8;

const int minMoveTime = 130;

const int minServing = 300;

const int ledPin = 13;

int servingTime = 1400; // 1000 – 9g, 2000 – 18g, 3000 – 27g, 3500 – 31g, 4000 - 36g

int servingDelta = 150;

int jerkPeriod = 700; // 1000/1000 – 9g, 1000/500 – 7g, 1400/700 – 12g

unsigned long lastFeeding=0;

void setup ()

{

Serial.begin(57600);

Serial.println();

pinMode(ledPin, OUTPUT);

digitalWrite(ledPin, LOW);

pinMode(buttonDispensePin, INPUT_PULLUP);

pinMode(buttonMinusPin, INPUT_PULLUP);

pinMode(buttonPlusPin, INPUT_PULLUP);

buzz();

}

void loop ()

{

int dispenseVal = digitalRead(buttonDispensePin);

if(dispenseVal == LOW)

dispense(servingTime);

int plusVal = digitalRead(buttonPlusPin);

if(plusVal == LOW){

delay(1000);

int plusVal = digitalRead(buttonPlusPin);

if(plusVal == LOW){

servingTime+=servingDelta;

buzz();

}

}

int minusVal = digitalRead(buttonMinusPin);

if(minusVal == LOW){

delay(1000);

int minusVal = digitalRead(buttonMinusPin);

if(minusVal == LOW){

servingTime-=servingDelta;

servingTime = servingTime<minServing?minServing:servingTime;

buzz();

}

}

if(millis() - lastFeeding>12*hour)//

dispense(servingTime);

delay(10);

}

#define countof(a) (sizeof(a) / sizeof(a[0]))

void dispense(int dispenseTime)

{

lastFeeding = millis();

servo.attach(servoPin);

for(;dispenseTime>0;dispenseTime-=jerkPeriod){

servo.write(100);

delay(dispenseTime>=jerkPeriod ? jerkPeriod : dispenseTime%1000);

jerk();

}

stop();

}

void jerk()

{

servo.write(0);

delay(minMoveTime);

servo.write(180);

delay(minMoveTime);

}

void stop()

{

servo.write(90);

delay(100);

servo.detach();

}

void buzz()

{

servo.attach(servoPin);

servo.write(0);

delay(50);

jerk();

jerk();

stop();

}

Kode – batteri med knapp

#include <Servo.h>

#include <LowPower.h>

#include <EventManager.h>

#include <ThreeWire.h>

#include <RtcDS1302.h>

#define EI_NOTINT0

#define EI_NOTINT1

#define EI_ARDUINO_INTERRUPTED_PIN

#include <EnableInterrupt.h>

#define minute 60

#define hour 3600

#define timerDelta 10000

#define servoPin 9

#define servoPowerPin 10

#define buttonDispensePin 7

#define buttonMinusPin 6

#define buttonPlusPin 8

#define buttonPin 5

#define ledPin 13

#define limit10m 2

#define limit24h 6

#define minMoveTime 130

#define minServing 300

EventManager eventManager;

Servo servo;

ThreeWire myWire(A1,A2,A0); // DAT, CLK, RST

RtcDS1302<ThreeWire> Rtc(myWire);

int servingTime = 800; // 1000 – 9g, 2000 – 18g, 3000 – 27g, 3500 – 31g, 4000 - 36g

int jerkPeriod = 800; // 1000/1000 – 9g, 1000/500 – 7g, 1400/700 – 12g

int servingDelta = 150;

int times10m = 0;

int times24h = 0;

uint32_t lastFeeding=0;

void setup()

{

pinMode(buttonDispensePin, INPUT_PULLUP);

pinMode(buttonMinusPin, INPUT_PULLUP);

pinMode(buttonPlusPin, INPUT_PULLUP);

pinMode(buttonPin, INPUT_PULLUP);

pinMode(servoPowerPin, OUTPUT);

pinMode(ledPin, OUTPUT);

digitalWrite(ledPin, LOW);

servoDetach();

enableInterrupt(buttonPin, interruptHandler, FALLING);

eventManager.addListener( buttonPin, buttonListener );

Serial.begin(9600);

setupTime();

buzz();

}

void setupTime()

{

Rtc.Begin();

RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);

Serial.print("Compiled: ");

printDateTime(compiled);

Serial.println();

Serial.print("RTC: ");

RtcDateTime now = Rtc.GetDateTime();

printDateTime(now);

Serial.println();

if (Rtc.GetIsWriteProtected())

{

Serial.println("RTC was write protected, enabling writing now");

Rtc.SetIsWriteProtected(false);

}

if (!Rtc.GetIsRunning())

{

Serial.println("RTC was not actively running, starting now");

Rtc.SetIsRunning(true);

}

if (now < compiled)

{

Serial.println("RTC is older than compile time! (Updating DateTime)");

Rtc.SetDateTime(compiled);

}

else if (now > compiled)

{

Serial.println("RTC is newer than compile time. (this is expected)");

}

else if (now == compiled)

{

Serial.println("RTC is the same as compile time! (not expected but all is fine)");

}

}

#define countof(a) (sizeof(a) / sizeof(a[0]))

void printDateTime(const RtcDateTime& dt)

{

char datestring[20];

snprintf_P(datestring,

countof(datestring),

PSTR("%02u/%02u/%04u %02u:%02u:%02u"),

dt.Month(),

dt.Day(),

dt.Year(),

dt.Hour(),

dt.Minute(),

dt.Second() );

Serial.print(datestring);

}

void interruptHandler()

{

eventManager.queueEvent(arduinoInterruptedPin, 0);

}

void buttonListener(int, int)

{

RtcDateTime nowT = Rtc.GetDateTime();

// Serial.print("Event: ");

// printDateTime(nowT);

// Serial.println();

uint32_t now = nowT.TotalSeconds();

if(now-lastFeeding < 5)

return; // Simple debouncer

if(now-lastFeeding > 10*minute)

times10m = 0;

if(now-lastFeeding > 24*hour)

times24h = 0;

bool allowed = times10m<limit10m && times24h<limit24h;

// Serial.print(times10m);

// Serial.print(" ");

// Serial.print(times24h);

// Serial.print(" ");

// Serial.print(now);

// Serial.print(" ");

// Serial.println(allowed);

if(!allowed) return;

lastFeeding = now;

dispense(servingTime);

times10m++;

times24h++;

}

void loop() {

LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_ON);

eventManager.processAllEvents();

}

void servoAttach()

{

servo.attach(servoPin);

digitalWrite(servoPowerPin, HIGH);

delay(200);

}

void servoDetach()

{

pinMode(servoPin, INPUT);

digitalWrite(servoPowerPin, LOW);

}

void dispense(int dispenseTime)

{

servoAttach();

for(;dispenseTime>0;dispenseTime-=jerkPeriod){

servo.write(100);

delay(dispenseTime>=jerkPeriod ? jerkPeriod : dispenseTime%1000);

jerk();

}

stop();

}

void jerk()

{

servo.write(0);

delay(minMoveTime);

servo.write(180);

delay(minMoveTime);

}

void stop()

{

servo.write(90);

delay(100);

servoDetach();

}

void buzz()

{

servoAttach();

servo.write(0);

delay(50);

jerk();

jerk();

stop();

}

Written by Alexander U. and Roger Rosmo.
Last updated 06/03/2025