Comedero para mascotas con piezas impresas en 3D
Componentes y suministros
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 6 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 5 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 |
Herramientas y máquinas necesarias
| ||||
| ||||
|
Acerca de este proyecto
La historia detrás del comedero para mascotas
He estado jugando con microcontroladores durante un tiempo y quería intentar diversificarme de los tutoriales. Antes de Covid-19, realmente no tenía un horario establecido y trabajaba muchas horas. El horario de alimentación de mi perro realmente estaba empezando a sufrir y estaba provocando algunas molestias visibles. Mi área también es propensa a sufrir inundaciones durante la temporada de huracanes. Desafortunadamente, hubo un par de casos en los que no pude regresar a casa para alimentar a mi hija. Necesitaba encontrar una solución para que, si no pudiera llegar a casa, mi perro no pasara hambre. En lugar de comprar uno por $ 30- $ 40, ¿por qué no construir uno por $ 100 o más? Es una broma.
¡El alimentador!
Mi comedero para perros usa un microcontrolador Arduino. Había algunas características clave que necesitaba que la mayoría de las otras versiones de comederos para perros no tenían. Es decir, una solución para la recuperación de cortes de energía alimentando y abordando el problema de que los alimentos se atasquen en el mecanismo de dispensación. Mi área también sufre cortes de energía aleatorios. Las interrupciones nunca suelen durar mucho, por lo que no vi la necesidad de redundancia de energía. También quería algo que fuera fácil de desmontar y limpiar. Tenga cuidado al lavar las piezas impresas en 3D, puede usar agua tibia, NO agua caliente a menos que planee imprimir con ABS o PET-G.
La lista completa de características es la siguiente:
- Dos tomas por día
- Cronometraje preciso con reloj en tiempo real
- Cambio de hora manual del reloj en tiempo real
- Opción de alimentación manual
- Indicación LED de sensor Hall y falla del reloj en tiempo real
- Descripción general de los tiempos de alimentación, la hora actual y las finalizaciones de alimentación en la pantalla principal
- Menú fácil de navegar
- Reanudación de la alimentación por cortes de energía (se alimentará cuando vuelva la energía)
- Los tiempos de alimentación y las finalizaciones se almacenan de forma segura en EEPROM
- El servo "sacude" en caso de que la comida se atasque durante la dispensación
Demostración
¡Próximamente el video de demostración!
¿Qué más necesitas?
- 4 tornillos M3-0,5 x 16 mm (rueda codificadora)
- 4 tornillos M3-0,5 x 10 mm (soporte de servo)
- 12 tuercas hexagonales M3
- 4 tuercas de seguridad M3
- Tornillos multiusos para madera de 16 x 1-1 / 2 pulgadas (38 mm)
- Pegamento para madera
- 6 imanes de 0,315 x 0,118 pulgadas (8 x 3 mm)
- Madera de 1-1 / 2 x 1-1 / 2 pulgadas (3.8 x 3.8 cm) para patas y montaje de servo
- Madera de 1/2 x 6 pulgadas (1,27 x 15,24 cm) para la base y el respaldo
Las piezas impresas en 3D
Recientemente obtuve una impresora 3D y pensé, qué mejor manera de aprender y ponerla en uso que imprimiendo piezas personalizadas para mi comedero para mascotas. Todo lo que se imprime es PLA + y se imprimió con una Ender 3 Pro con Cura como cortadora. Los STL para todas las partes se pueden encontrar en la página del proyecto Github. Enlace abajo. Lea el archivo README para ver las instrucciones de impresión y la configuración de la segmentación.
La vivienda
La carcasa está diseñada pensando en la comodidad y la sencillez. No se necesitan tornillos ni tuercas. Simplemente introduzca y extraiga los componentes. Cada inserto tiene 4 pestañas que mantienen los componentes en su lugar.
Aunque inicialmente estropeé la ranura de la pantalla LCD, volví y arreglé el modelo en Fusion 360. Era demasiado vago para volver atrás y reimprimirlo. Usé algunos tornillos M3 de 0.5 x 6 mm adicionales que tenía tirados para asegurarlo. Hay cuatro espaciadores en cada esquina de la ranura de la pantalla LCD con orificios para que pueda asegurar la pantalla si es necesario. Desafortunadamente, nunca tomé fotografías de la tapa del gabinete antes de poner todo.
El accesorio de la rueda del codificador
La rueda del codificador de rueda tiene dos propósitos:
- Proporciona al Arduino información sobre la posición del servo
- Coloca la paleta de goma en el servo
Una porción de alimentación equivale a 1/6 (60 grados) de giro de la rueda. Use una balanza de alimentos para medir la cantidad de alimento que recibe su mascota por comida y luego ajuste el número de la porción hasta que obtenga un rango que satisfaga esa cantidad. Creo que una comida para mí fue de unos 173 gramos de comida. Un tamaño de porción de 17 me dio un rango de 170-177 gramos por toma. Todo depende del tamaño de tu croqueta. Asegúrese de que la bocina esté colocada entre las 8 tuercas hexagonales M3.
El soporte del servo y el soporte del sensor Hall
Este soporte de servo personalizado también sostiene el sensor Hall y monta el servo en una pieza de madera de 1-1 / 2 x 1-1 / 2 pulgadas (3.8 x 3.8 cm). La longitud de la madera dependerá de dónde se encuentre el servo (más sobre eso más adelante). Hay mucho margen de error con la montura, así que no se preocupe demasiado por obtener la longitud perfecta.
El embudo, el conducto de alimentos y el riel del conducto
Esto constituye el sistema de entrega de alimentos. La comida desciende del dispensador a través del embudo y llega a la rampa y al recipiente de comida. Desafortunadamente, no tomé fotografías del riel de la rampa antes de montarlo.
El marco
* Renuncia de responsabilidad * No tomé fotografías de cada paso del proceso de montaje. Algunas de estas imágenes habrán omitido pasos, pero aún así lo llevaré paso a paso para el ensamblaje del marco.
Asegúrese de perforar previamente los orificios para cualquier tornillo que utilice. ¡No quiere cortar la madera!
Este es el dispensador de cereales que compré en Amazon. Hay algunos que tienen varios dispensadores si tienes varias mascotas y necesitas varios comederos, yo solo necesitaba uno. La marca es Honey Can Do, pero estoy seguro de que cualquier marca funcionará.
Lo primero que comencé fue a quitar la perilla de la varilla que conectaba con la rueda de paletas del dispensador. Usé una sierra de mano para quitar la perilla. Puede utilizar una herramienta eléctrica si tiene una. No corte la varilla más abajo donde termina la parte plateada. Corta justo en la base de la perilla.
Después de cortar la perilla, retire el material restante cortando los 3 soportes que se ven en la última imagen de arriba. Esto tomó bastante tiempo. Imagino que será más rápido si tiene la herramienta eléctrica adecuada. Una vez que retire esos tres soportes, la pieza restante que queda en la varilla debe desprenderse con un poco de grasa para los codos. Tendrá que lijar la parte de la varilla más cercana a la perilla para que encaje bien en la rueda del codificador.
A continuación, comenzamos a construir la base del marco. Tomé la madera de 1/2 x 6 pulgadas (1,27 x 15,24 cm) y la corté en dos piezas de 8 pulgadas (20,32 cm). Esto formará la base y la parte posterior del marco del alimentador. Aplica un poco de pegamento para madera y usa 2 de los clavos multiusos para unirlos en forma de L. Deberá agregar un par de soportes en ángulo recto para reforzar el panel posterior y la conexión del panel inferior. Use 4 tornillos multiusos y un poco de pegamento para madera para asegurarlos a los paneles posterior e inferior. No tengo una imagen de este paso, pero puedes verlas en las imágenes más abajo.
Desde aquí, debe cortar el soporte del dispensador a una altura de aproximadamente 4,5 pulgadas (11,43 cm). No tiene por qué ser perfecto. Trate de acercarlo lo más que pueda. El embudo permite algo de margen de maniobra. Una vez que haya cortado el soporte a la medida, colóquelo contra la parte posterior y asegúrese de que quede plano sobre la base. Una vez que lo tenga en su lugar, use un lápiz o bolígrafo para marcar el centro de donde estará el extremo dispensador del recipiente. Luego, deberá perforar un orificio de 5,08 cm (2 pulgadas) a lo largo del panel de la base. Es fundamental que mida dos veces y corte una vez con este. Ese será el agujero que se ve en la imagen de arriba.
Una vez que haya perforado el orificio en el panel de la base, estamos listos para colocar el soporte del dispensador en el panel posterior. Lo que desea hacer es colocar el soporte contra el panel posterior (que se muestra en la primera imagen a continuación). Hay dos áreas de cubículos debajo del anillo del soporte del dispensador. Aquí es donde desea que estén los agujeros (vea la imagen a continuación). Use un lápiz o bolígrafo para marcar la altura de donde deberían estar los dos agujeros en el panel posterior. Quieres que estén lo más cerca posible del centro del soporte del dispensador. Hay dos tornillos que conectan el anillo superior del soporte a la parte inferior que cortaste. Tenga cuidado de no golpearlos al perforar. Nuevamente, recuerde medir dos veces y perforar una vez. O dos veces, en este caso.
Ahora que tenemos los orificios perforados, podemos fijar el soporte al panel posterior usando los pernos hexagonales de 5/16 de pulgada, las tuercas hexagonales de 5/16 de pulgada y las arandelas de 5/16 de pulgada. Desea continuar y empujar los pernos asegurándose de colocar arandelas en el extremo hexagonal de los pernos antes de empujarlos. Después de que salgan por el otro lado, coloque el otro juego de arandelas en el lado roscado y luego comience a apretar a mano las tuercas hexagonales. Esto va a ser un poco complicado. Después de apretar las tuercas a mano, tendrá que usar un dado para sujetar la tuerca y apretarlas más. Solo alrededor de 1/4 de vuelta más o menos. Tenga cuidado de no apretar demasiado.
Ahora que el soporte está asegurado de forma segura, podemos agregar la pieza de madera de 1/2 x 1/2 pulgada en la que se sentará el servo. La duración de esto dependerá de dónde se colocará su servo. Continúe y monte el mecanismo de alimentación conectando el servo a la bocina en la rueda del codificador y la rueda del codificador a la paleta de goma dentro del recipiente de plástico. Coloque el recipiente en el anillo superior y mida dónde se encuentra el servo desde el panel de la base. No te preocupes por que sea perfecto. El soporte del servo permite bastante margen de maniobra. Use pegamento para madera y un solo tornillo multiusos para asegurar el soporte de madera.
El siguiente paso es unir las patas al panel base del alimentador. La longitud de las patas dependerá de la altura del plato de comida de su mascota. Mi perro tiene su plato de comida en un soporte elevado; por lo tanto, necesitaba que mi alimentador estuviera bastante alto. Use 4 de los tornillos multiusos y un poco de pegamento para madera para asegurarlos en su lugar. Recomiendo colocar una viga transversal entre las dos patas delanteras y dos traseras y otra viga transversal entre las vigas transversales como se muestra a continuación para mayor estabilidad. Utilice un total de 6 tornillos multiusos y un poco de pegamento para madera para asegurar las piezas a las patas.
Los siguientes pasos son:
- inserte el embudo en el orificio que perforamos en el panel de la base
- coloque el servo en el soporte del servo
- asegure el soporte al soporte de madera
Fije el servo al soporte con los 4 tornillos M3 x 10 mm y 4 de las tuercas hexagonales M3. Una vez que el servo está asegurado, podemos asegurar el soporte en el soporte del servo de madera. Utilice dos de los tornillos multiusos para fijar ligeramente el soporte en el soporte de madera. No apriete demasiado o dañará el soporte. Asegúrese de seguir los pasos en ese orden. El embudo elevará un poco el servo y se ajusta bastante bien al final del recipiente de plástico, por lo que es imposible colocarlo si el recipiente de plástico ya está en el soporte del anillo superior.
Los pasos finales serán colocar el soporte deslizante de la rampa y la corredera en sí. Desea colocar el soporte ligeramente detrás del orificio en el panel de la base. Lo desea lo más adelante posible para que la diapositiva salga del marco del alimentador. Utilice dos de los tornillos multiusos para asegurar el soporte a la parte inferior del panel de base del marco. Es mejor hacer esto con la diapositiva en el soporte, ya que el soporte tiene algo de flexibilidad y querrá un ajuste bastante seguro entre la corredera y el soporte.
La electrónica
Desafortunadamente, nunca tomé fotografías del proceso de soldadura. Sin embargo, no hay mucho que hacer. Simplemente suelde cada componente a sus pines correspondientes y estará listo para comenzar. Si desea utilizar encabezados de pin en su lugar, también puede hacerlo de esa manera. Hay suficiente espacio debajo y por encima de la ranura Arduino para permitir los pines y conectores. Definitivamente recomiendo soldar todo junto antes de colocar todos los componentes en sus respectivas ranuras.
Encendí mi Arduino a través del pin de alimentación externo no regulado (pin 30). Esto requiere un voltaje de entrada entre 7-20 voltios porque este voltaje se alimenta a través del regulador integrado de Arduino. Si desea alimentarlo a través de USB, solo debe asegurarse de que lo está suministrando con 5 Voltios, NO con los 7-20 Voltios.
Asegúrese de soldar la resistencia de 10k Ohm entre los pines Vcc y Signal en el sensor de pasillo. De lo contrario, no obtendrá una lectura. Además, no olvide conectar todos los componentes a una tierra común. Cometí el error de perder uno de los terrenos y mi sistema funcionaría por un tiempo, pero el sensor de pasillo eventualmente comenzaría a fallar. Fue un muy buen día para mi perro.
Terminé haciendo conectores personalizados para el sensor Hall y las conexiones del servo. Soldé los cables a los cabezales de clavija macho. Estos cuelgan de la parte inferior del recinto. Para el sensor de pasillo, hice un adaptador hembra personalizado cortando, pelando y soldando algunos cables del conector Dupont que tenía por ahí.
Para el Vcc y el riel de tierra, corté la sección del riel de potencia de una placa de pruebas perma-proto adicional que tenía por ahí. Cualquier cosa similar que tenga a su alrededor funcionará. Simplemente verifique sus conexiones antes de colocar el riel en su ranura. Esto es lo más difícil de sacar una vez que está adentro. También lo aprendí de la manera más difícil.
¡Eso es! Espero que se diviertan tanto construyendo esto como yo. Parece desafiante al principio, pero cuanto más te adentras, más fácil se vuelve. Avísame si tienes alguna pregunta sobre algo. ¡Feliz retoques!
Código
- Código del alimentador
Código del alimentador C / C ++
// Versión final para el comedero para mascotas / * Características:- Menú fácil de navegar - Resumen de los tiempos de alimentación, la hora actual, las opciones de alimentación y la porción de alimentación en la pantalla principal - Porciones controlables mediante un sensor de pasillo para retroalimentación - Preciso mantenimiento del tiempo con el chip DS1307 - Puede cambiar manualmente el tiempo establecido en el chip DS1307 - Dos alimentaciones por día - Opción de alimentación manual - Reinicio de la alimentación en caso de corte de energía - Indicación LED del sensor de pasillo y reloj de tiempo real - Los tiempos de alimentación y las terminaciones se almacenan de forma segura en EEPROM - Servo "sacude" en caso de que la comida se atasque * / # include#include #include #include #include # include #include // Crea un objeto servo para controlar su servoServo myservo; // Asignar todos mis pines de entrada y E / S # definir ENTER_BUTTON_PIN 11 # definir UP_BUTTON_PIN 10 # definir DOWN_BUTTON_PIN 9 # definir BACK_BUTTON_PIN 8 # definir POWER_LED_PIN 5 # definir MANUAL_BUTTON_PIN A1 # definir hallPin 2 # definir HALL_LE D_PIN 7 # define SERVO_PIN 6 // Definiendo todos los Botones, funciona con la librería JC_ButtonButton enterBtn (ENTER_BUTTON_PIN); Botón upBtn (UP_BUTTON_PIN); Botón downBtn (DOWN_BUTTON_PIN); Botón backBtn (BACKIN_BUTTON_PIN manual) LCD I2C y RTCLiquidCrystal_I2C lcd (0x27, 16, 2); RTC_DS1307 rtc; // Variables utilizadas a lo largo del código largo hallSensorTime; unsigned long rotationTime =400; unsigned long led_previousMillis =0; const long interval_delay =1000; unsigned long delay_interval =2000; int ledState =LOW; boolean manualFeed =false; boolean hall_sensor_fail =false; unsigned long blink_previousMillis =0; unsigned long blink_currentMillis =0; unsigned long blink_interval =500; unsigned long delay_currentMillis =0; unsigned long delay_previousMillis =falso; booleano int count; feed1_complete booleano =falso; feed2_complete booleano =falso; feed1_trigger booleano =falso; feed2_trigger booleano =falso; servoOn booleano =tru e; // Interrupción del sensor Hall volátil booleano hallSensorActivated =false; volatile int isr_count =1; void hallActiveISR () {hallSensorActivated =true; digitalWrite (HALL_LED_PIN, HIGH); isr_count =isr_count + 1;} / * Aquí utilizo enumeraciones para realizar un mejor seguimiento de qué botón se está presionando en lugar de tener cada botón configurado en un valor entero. * / enum {btnENTER, btnUP, btnDOWN, btnBACK,}; / * Estados de la máquina de estados. Lo mismo aquí con el tipo de enumeración. Hace que sea más fácil realizar un seguimiento del menú en el que se encuentra o al que desea ir en lugar de darle a cada menú un valor de entrada * / enum STATES {MAIN, MENU_EDIT_FEED1, MENU_EDIT_FEED2, MENU_EDIT_TIME, MENU_EDIT_PORTION, EDIT_FEED1_HOUR, EDIT_FEED1_MIN_FEED_HOUR, EDIT_FEED1_MIN_FEED_HOUR , EDIT_MIN, EDIT_PORTION}; // Mantiene el estado de la máquina de estado STATES state; // Variables de entrada del usuario en hora; int Minuto; int porción; int feed_time1_hour; int feed_time1_min; int feed_time2_hour; int feed_time2_min; int userInput; // Carácter especial check markbyte check_Char [8] ={B00000, B00000, B00001, B00011, B10110, B11100, B11000, B00000}; // ======================La configuración ===========================void setup () {Wire.begin (); Serial.begin (9600); lcd.init (); LCD luz de fondo(); lcd.createChar (0, check_Char); if (! rtc.begin ()) {Serial.println ("¡No se pudo encontrar RTC!"); } // rtc.adjust (DateTime (F (__ DATE__), F (__ TIME__))); // Los botones enterBtn.begin (); upBtn.begin (); downBtn.begin (); backBtn.begin (); manualFeedBtn.begin (); // Configurando el estado inicial de State Machine state =MAIN; // Configuración de entradas y salidas pinMode (POWER_LED_PIN, OUTPUT); pinMode (HALL_LED_PIN, SALIDA); pinMode (hallPin, ENTRADA); / * Adjuntar interrupción al pin que se conecta al sensor de pasillo. El sensor de pasillo que utilicé está configurado en ALTO y pasa a BAJO cuando encuentra un imán. Es por eso que está configurado en FALLING * / attachInterrupt (digitalPinToInterrupt (hallPin), hallActiveISR, FALLING); // estado predeterminado de los LED digitalWrite (POWER_LED_PIN, HIGH); digitalWrite (HALL_LED_PIN, LOW); / * Estas funciones obtienen el tiempo de alimentación almacenado, la alimentación completa y el tamaño de la porción de EEPROM al inicio. Hice esto porque tengo cortes de energía aleatorios aquí donde vivo. * / get_feed_time1 (); get_feed_time2 (); get_completed_feedings (); get_portion ();} // ======================El bucle ===========================bucle vacío () {cambio_estados (); check_buttons (); check_feedtime (); check_rtc (); manual_feed_check ();} // ===============Las funciones =======================/ * Usos la biblioteca JC_Button para verificar continuamente si se presionó un botón. Dependiendo del botón que se presione, establece la variable userInput que se usará en la función menu_transitions * / void check_buttons () {enterBtn.read (); upBtn.read (); downBtn.read (); backBtn.read (); manualFeedBtn.read (); if (enterBtn.wasPressed ()) {Serial.println ("You Pressed Enter!"); userInput =btnENTER; menu_transitions (userInput); } if (upBtn.wasPressed ()) {Serial.println ("¡Presionaste hacia arriba!"); userInput =btnUP; menu_transitions (userInput); } if (downBtn.wasPressed ()) {Serial.println ("¡Has presionado hacia abajo!"); userInput =btnDOWN; menu_transitions (userInput); } if (backBtn.wasPressed ()) {Serial.println ("¡Has presionado hacia atrás!"); userInput =btnBACK; menu_transitions (userInput); } if (manualFeedBtn.wasPressed ()) {Serial.println ("¡Está alimentando manualmente!"); manualFeed =verdadero; }} // ===================================================/ * Esta función determina lo que se muestra, dependiendo del menú o "estado" en el que se encuentre. Cada menú tiene una función que muestra el menú respectivo * / void Changing_states () {switch (state) { case MAIN:display_current_time (); display_feeding_times (); display_portion (); descanso; case MENU_EDIT_FEED1:display_set_feed_time1_menu (); descanso; case MENU_EDIT_FEED2:display_set_feed_time2_menu (); descanso; case MENU_EDIT_TIME:display_set_time_menu (); descanso; case MENU_EDIT_PORTION:display_set_portion_menu (); descanso; caso EDIT_FEED1_HOUR:set_feed_time1 (); descanso; case EDIT_FEED1_MIN:set_feed_time1 (); descanso; caso EDIT_FEED2_HOUR:set_feed_time2 (); descanso; case EDIT_FEED2_MIN:set_feed_time2 (); descanso; case EDIT_HOUR:set_the_time (); descanso; caso EDIT_MIN:set_the_time (); descanso; case EDIT_PORTION:establecer_la_porción (); descanso; }} // ===================================================/ * Esta es la parte de transición de la máquina de estado. Esto es lo que le permite ir de un menú a otro y cambiar valores * / void menu_transitions (int input) {switch (state) {case MAIN:if (input ==btnENTER) {lcd.clear (); estado =MENU_EDIT_FEED1; } if (input ==btnBACK) {hall_sensor_fail =false; } descanso; // ------------------------------------------------ ---- case MENU_EDIT_FEED1:if (input ==btnBACK) {lcd.clear (); estado =PRINCIPAL; } más si (entrada ==btnENTER) {lcd.clear (); estado =EDIT_FEED1_HOUR; } más si (entrada ==btnDOWN) {lcd.clear (); estado =MENU_EDIT_FEED2; } descanso; // ------------------------------------------------ ---- case EDIT_FEED1_HOUR:// Necesito esto para evitar que el servo se apague mientras se configura el tiempo servoOn =false; if (input ==btnUP) {feed_time1_hour ++; if (feed_time1_hour> 23) {feed_time1_hour =0; }} else if (input ==btnDOWN) {feed_time1_hour--; if (feed_time1_hour <0) { feed_time1_hour =23; } } else if (input ==btnBACK) { lcd.clear(); servoOn =true; state =MENU_EDIT_FEED1; } else if (input ==btnENTER) { state =EDIT_FEED1_MIN; } break; //---------------------------------------------------- case EDIT_FEED1_MIN:if (input ==btnUP) { feed_time1_min++; if (feed_time1_min> 59) { feed_time1_min =0; } } else if (input ==btnDOWN) { feed_time1_min--; if (feed_time1_min <0) { feed_time1_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED1_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor (0, 0); lcd.print( "*Settings Saved*"); retraso (1000); lcd.clear (); servoOn =true; write_feeding_time1 (); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_FEED2:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_FEED1; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_FEED2_HOUR; } else if (input ==btnDOWN) { lcd.clear(); state =MENU_EDIT_TIME; } break; //---------------------------------------------------- case EDIT_FEED2_HOUR:servoOn =false; if (input ==btnUP) { feed_time2_hour++; if (feed_time2_hour> 23) { feed_time2_hour =0; } } else if (input ==btnDOWN) { feed_time2_hour--; if (feed_time2_hour <0) { feed_time2_hour =23; } } else if (input ==btnBACK) { lcd.clear(); servoOn =true; state =MENU_EDIT_FEED2; } else if (input ==btnENTER) { state =EDIT_FEED2_MIN; } break; //---------------------------------------------------- case EDIT_FEED2_MIN:if (input ==btnUP) { feed_time2_min++; if (feed_time2_min> 59) { feed_time2_min =0; } } else if (input ==btnDOWN) { feed_time2_min--; if (feed_time2_min <0) { feed_time2_min =59; } } else if (input ==btnBACK) { state =EDIT_FEED2_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor (0, 0); lcd.print( "*Settings Saved*"); retraso (1000); lcd.clear (); servoOn =true; write_feeding_time2 (); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_TIME:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_FEED2; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_HOUR; } else if (input ==btnDOWN) { lcd.clear(); state =MENU_EDIT_PORTION; } break; //---------------------------------------------------- case EDIT_HOUR:if (input ==btnUP) { Hour++; if (Hour> 23) { Hour =0; } } else if (input ==btnDOWN) { Hour--; if (Hour <0) { Hour =23; } } else if (input ==btnBACK) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { state =EDIT_MIN; } break; //---------------------------------------------------- case EDIT_MIN:if (input ==btnUP) { Minute++; if (Minute> 59) { Minute =0; } } else if (input ==btnDOWN) { Minute--; if (Minute <0) { Minute =59; } } else if (input ==btnBACK) { state =EDIT_HOUR; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor (0, 0); lcd.print( "*Settings Saved*"); retraso (1000); lcd.clear (); rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); state =MAIN; } break; //---------------------------------------------------- case MENU_EDIT_PORTION:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_PORTION; } break; //---------------------------------------------------- case EDIT_PORTION:if (input ==btnUP) { portion++; if (portion> 20) { portion =1; } } else if (input ==btnDOWN) { portion--; if (portion <1) { portion =20; } } else if (input ==btnBACK) { lcd.clear(); state =MENU_EDIT_PORTION; } else if (input ==btnENTER) { lcd.clear(); lcd.setCursor (0, 0); lcd.print( "*Settings Saved*"); retraso (1000); lcd.clear (); write_portion(); state =MAIN; } break; }}//=====================================================// This function checks the feed time against the current timevoid check_feedtime (){ DateTime now =rtc.now(); if (now.second() ==0) { if ((now.hour() ==feed_time1_hour) &&(now.minute() ==feed_time1_min)) { feeding1_trigger =true; if (servoOn) { if (feeding1_complete ==false) { lcd.clear(); lcd.setCursor (3, 0); lcd.print ("Dispensing"); lcd.setCursor (1, 1); lcd.print("First Feeding"); startFeeding(); } } } else if ((now.hour() ==feed_time2_hour) &&(now.minute () ==feed_time2_min)) { feeding2_trigger =true; if (servoOn) { if ( feeding2_complete ==false) { lcd.clear(); lcd.setCursor (3, 0); lcd.print ("Dispensing"); lcd.setCursor (0, 1); lcd.print("Second Feeding"); startFeeding(); } } } } // Midnight Reset if ( (now.hour() ==0) &&(now.minute() ==0)) { feeding1_complete =false; feeding2_complete =false; EEPROM.write(4, feeding1_complete); EEPROM.write(5, feeding2_complete); } /*If power outtage happens during a feed time, this checks to see if the feed time has passed and if the feeding occurred. If not, it feeds. */ if ( (now.hour()>=feed_time1_hour) &&(now.minute()> feed_time1_min)) { if ((feeding1_complete ==0) &&(feeding1_trigger ==0)) { lcd.clear(); lcd.setCursor(5, 0); lcd.print ("Uh-Oh!"); lcd.setCursor(2, 1); lcd.print("Power Outage"); startFeeding(); } } if ( (now.hour()>=feed_time2_hour) &&(now.minute()> feed_time2_min)) { if ((feeding2_complete ==0) &&(feeding2_trigger ==0)) { lcd.clear(); lcd.setCursor(5, 0); lcd.print ("Uh-Oh!"); lcd.setCursor(2, 1); lcd.print("Power Outage"); startFeeding(); } }}//=====================================================// Displays the set portion menu optionvoid display_set_portion_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor (0, 1); lcd.print("Set the Portion");}//=====================================================// Displays the menu where you change the current timevoid set_the_time (){ lcd.setCursor(2, 0); lcd.print("Set the Time"); switch (state) { //---------------------------------------------------- case EDIT_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(Hour); } else { lcd.setCursor(5, 1); lcd.print (""); } lcd.print(":"); add_leading_zero(Minute); descanso; //---------------------------------------------------- case EDIT_MIN:lcd.setCursor(5, 1); add_leading_zero(Hour); lcd.print (":"); if (blink_state ==0) { lcd.setCursor(8, 1); add_leading_zero(Minute); } else { lcd.setCursor(8, 1); lcd.print (""); } break; } blinkFunction();}//=====================================================// Displays the menu where you change the feeding portionvoid set_the_portion (){ lcd.setCursor (0, 0); lcd.print("Set the Portion"); switch (state) { case EDIT_PORTION:if (blink_state ==0) { lcd.setCursor(7, 1); add_leading_zero(portion); } else { lcd.setCursor(7, 1); lcd.print (""); } } blinkFunction();}//=====================================================//Displays the menu option for setting the timevoid display_set_time_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor(2, 1); lcd.print("Set the Time");}//=====================================================// Displays the menu where you change the second feeding timevoid set_feed_time2 (){ lcd.setCursor(0, 0); lcd.print("Set Feed Time 2"); switch (state) { //---------------------------------------------------- case EDIT_FEED2_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(feed_time2_hour); } else { lcd.setCursor(5, 1); lcd.print (""); } lcd.print(":"); add_leading_zero(feed_time2_min); descanso; //---------------------------------------------------- case EDIT_FEED2_MIN:lcd.setCursor(5, 1); add_leading_zero(feed_time2_hour); lcd.print (":"); if (blink_state ==0) { lcd.setCursor(8, 1); add_leading_zero(feed_time2_min); } else { lcd.setCursor(8, 1); lcd.print (""); } break; } blinkFunction();}//=====================================================// Displays the menu where you change the first feeding timevoid set_feed_time1 (){ lcd.setCursor(0, 0); lcd.print("Set Feed Time 1"); switch (state) { //---------------------------------------------------- case EDIT_FEED1_HOUR:if (blink_state ==0) { lcd.setCursor(5, 1); add_leading_zero(feed_time1_hour); } else { lcd.setCursor(5, 1); lcd.print (""); } lcd.print(":"); add_leading_zero(feed_time1_min); descanso; //---------------------------------------------------- case EDIT_FEED1_MIN:lcd.setCursor(5, 1); add_leading_zero(feed_time1_hour); lcd.print (":"); if (blink_state ==0) { lcd.setCursor(8, 1); add_leading_zero(feed_time1_min); } else { lcd.setCursor(8, 1); lcd.print (""); } break; } blinkFunction();}//=====================================================// Adds a leading zero to single digit numbersvoid add_leading_zero (int num) { if (num <10) { lcd.print("0"); } lcd.print(num);}//=====================================================/* Displays the feeding time on the main menu as well as the check mark for visual comfirmation of a completed feeding*/void display_feeding_times () { //Displaying first feed time lcd.setCursor(0, 0); lcd.print ("F1:"); add_leading_zero(feed_time1_hour); lcd.print (":"); add_leading_zero(feed_time1_min); lcd.print (""); if (feeding1_complete ==true) { lcd.write(0); } else { lcd.print(" "); } //Displaying second feed time lcd.setCursor(0, 1); lcd.print("F2:"); add_leading_zero(feed_time2_hour); lcd.print (":"); add_leading_zero(feed_time2_min); lcd.print (""); if (feeding2_complete ==true) { lcd.write(0); } else { lcd.print(" "); }}//=====================================================// Displays the current time in the main menuvoid display_current_time () { DateTime now =rtc.now(); lcd.setCursor(11, 0); add_leading_zero(now.hour()); lcd.print (":"); add_leading_zero(now.minute());}//=====================================================// Displays the menu option for setting the first feed timevoid display_set_feed_time1_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor (0, 1); lcd.print("Set Feed Time 1");}//=====================================================// Displays the meny option for setting the second feed timevoid display_set_feed_time2_menu () { lcd.setCursor(2, 0); lcd.print("Menu Options"); lcd.setCursor (0, 1); lcd.print("Set Feed Time 2");}//=====================================================// Displays the feeding portion in the main menuvoid display_portion (){ lcd.setCursor (12, 1); lcd.print("P:"); add_leading_zero(portion);}//=====================================================// Starts the feeding process.void startFeeding(){ // attach the servo to the pin myservo.attach(SERVO_PIN); count =1; hallSensorTime =millis(); // loop so that the servo runs until desired portion is reached while (count <=portion) { servoStart(); if (hallSensorActivated ==true) { // digitalWrite(LED_PIN,HIGH); count =count + 1; //resetting for next interrupt hallSensorTime =millis(); hallSensorActivated =false; digitalWrite(HALL_LED_PIN, LOW); } /* Moved the servo clockwise a bit to dislodge food stuck in the dispensing mechanism */ if (millis() - hallSensorTime> rotationTime) { hall_sensor_fail =true; Serial.println("I'm in Jiggle"); jiggle(); } } // Keeps track of which feeding just happened and writes it to EEPROM if ((feeding1_complete ==false) &&(feeding2_complete ==false)) { feeding1_complete =true; EEPROM.write(4, feeding1_complete); } else if ((feeding1_complete ==true) &&(feeding2_complete ==false)) { feeding2_complete =true; EEPROM.write(5, feeding2_complete); } servoStop(); digitalWrite(HALL_LED_PIN, LOW); /* Detaches the servo from the pin so that it is no longer recieving a signal. You may have to add a delay before this so the sensor stops when a magnet is over the hall sensor. There was significant momentum left in my system that I did not need it */ myservo.detach(); lcd.clear (); delay_currentMillis =millis(); while (millis() - delay_currentMillis <=delay_interval) { lcd.setCursor(2, 0); lcd.print ("Feeding Done"); } lcd.clear();}//=====================================================void servoStart(){ myservo.write(180);}//=====================================================void servoStop(){ // this value will vary, you have to find it through trial and error myservo.write(94);}//=====================================================// "jiggles" the servo in case food gets stuckvoid jiggle(){ myservo.write(80); delay(30); myservo.write(93); delay(30); myservo.write(180);}//=====================================================// Writes the hour and minute valies set for 1st feeding to the EEPROMvoid write_feeding_time1 (){ EEPROM.write(0, feed_time1_hour); EEPROM.write(1, feed_time1_min);}//=====================================================// Writes the hour and minute values set for 2nd feeding to the EEPROMvoid write_feeding_time2 () { EEPROM.write(2, feed_time2_hour); EEPROM.write(3, feed_time2_min);}//=====================================================// Writes portion value set to the EEPROMvoid write_portion (){ EEPROM.write(6, portion);}//=====================================================// Reads the hour and minute values from 1st feed time from EEPROMvoid get_feed_time1 (){ feed_time1_hour =EEPROM.read(0); if (feed_time1_hour> 23) feed_time1_hour =0; feed_time1_min =EEPROM.read(1); if (feed_time1_min> 59) feed_time1_min =0;}//=====================================================// Reads the hour and minute values from 2nd feed time from EEPROMvoid get_feed_time2 (){ feed_time2_hour =EEPROM.read(2); if (feed_time2_hour> 23) feed_time2_hour =0; feed_time2_min =EEPROM.read(3); if (feed_time2_min> 59) feed_time2_min =0;}//=====================================================// Reads portion set value from EEPROMvoid get_portion (){ portion =EEPROM.read(6);}//=====================================================// Reads boolean value of whether or not feedings have occured from EEPROMvoid get_completed_feedings(){ feeding1_complete =EEPROM.read(4); feeding2_complete =EEPROM.read(5);}//=====================================================/* Checks to see if the hall sensor has failed to read a magnet and blinks the red LED*/void check_hall_sensor () { if (hall_sensor_fail ==true) { if (blink_state ==0) { digitalWrite(HALL_LED_PIN, HIGH); } else { digitalWrite(HALL_LED_PIN, LOW); } blinkFunction(); } else { digitalWrite(HALL_LED_PIN, LOW); hall_sensor_fail =false; }}//=====================================================// Checks if you push the manual feed buttonvoid manual_feed_check () { if (manualFeed ==true) { lcd.clear(); lcd.setCursor (0, 0); lcd.print(" Manual Feeding"); startFeeding(); manualFeed =false; }}//=====================================================// checks to see if RTC is runningvoid check_rtc () { if (!rtc.isrunning()) { led_blink(); }}//=====================================================/* Blinks the red led when RTC has failed. Note:the led will be blinking at a different rate than when the hall sensor fails*/void led_blink(){ unsigned long led_currentMillis =millis(); if (led_currentMillis - led_previousMillis>=interval_delay) { led_previousMillis =led_currentMillis; if (ledState ==LOW) { ledState =HIGH; } else { ledState =LOW; } digitalWrite(HALL_LED_PIN, ledState); }}//=====================================================// Creates the blinking effect when changing valuesvoid blinkFunction(){ blink_currentMillis =millis(); if (blink_currentMillis - blink_previousMillis> blink_interval) { blink_previousMillis =blink_currentMillis; blink_state =!blink_state; }}//=====================================================
Link to Code on my Github
https://github.com/russo08/Pet-feeder/blob/main/feeder_final.inoPiezas y carcasas personalizadas
Fusion 360 and STL files on my Github
Here are all the fusion 360 files in case you want to customize them for different component sizes. I have also provided the STL files. The only model not on there is the tube for the hall sensor. That should be pretty easy to model and print.https://github.com/russo08/Pet-feeder.gitEsquemas
This is the circuit schematic. You can change it up if you need to. If you do, just remember to make the same adjustments in the code.Proceso de manufactura
- ¿Acabado de piezas impresas en 3D con… crayones?
- Sensor de seguimiento de línea con RPi
- API de sensor ambiental con un RPi
- Portenta y sensor de termopar (con MAX6675)
- Mejor control de calidad con piezas impresas en 3D
- ¡Abróchate el cinturón con piezas funcionales impresas en 3D!
- Re-imaginando el Go Kart con piezas impresas en 3D
- Seminario web:Impresión de piezas sólidas impresas en 3D con Eiger
- Drones extremos impresos en 3D
- La carrera hacia la meta con las piezas de vehículos SAE de fórmula impresa en 3D
- Refuerzo de piezas impresas en 3D con enrutamiento de fibra eficiente:Parte 1