Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Manufacturing Technology >> Processo de manufatura

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Componentes e Materiais

Componente Quantidade Observações
Arduino Nano R3 1
Módulo RTC DS1307 1 Pode usar qualquer marca de RTC, certamente há opções mais baratas
Módulo regulador de tensão LM-317 1
Servo de rotação contínua 1
Dispensador de cereal 1
Ímãs 8mm 6
Sensor Hall (unipolar) 1 Certifique-se que o sensor seja UNIPOLAR para não precisar da polaridade reversa do ímã para resetar a saída
LCD RGB Backlight Adafruit 16x2 1
Mochila I2C para LCD 1 Se preferir, pode comprar a tela LCD com a mochila já acoplada
LED 5mm Vermelho 1
LED 5mm Verde 1
Chave pushbutton momentânea 5
Adaptador fêmea para jack DC 1
Fonte 9-12V (wall wart) 1
Resistor 10k ohm 1 Para o sensor Hall. Conectar entre pinos Vcc e Sinal
Resistor 330 ohm 2 Para LEDs. Pode usar resistência maior se não quiser tão brilhante

Ferramentas e Máquinas Necessárias

Sobre o Projeto

A História por Trás do Alimentador

Tenho brincado com microcontroladores há um tempo e queria tentar sair dos tutoriais. Antes da Covid-19, eu não tinha uma agenda fixa e trabalhava muitas horas. A rotina de alimentação do meu cachorro estava realmente prejudicada e causando desconforto visível. Minha área também é propensa a inundações durante a temporada de furacões. Infelizmente, houve casos em que não consegui voltar para casa para alimentar minha velha cachorrinha. Precisava encontrar uma solução para que, se eu não pudesse chegar em casa, meu cachorro não passasse fome. Em vez de comprar um por $30-$40, por que não construir um por $100+? Brincadeira.

O Alimentador!

Meu alimentador de cachorro usa um microcontrolador Arduino. Havia alguns recursos-chave que eu precisava e que a maioria das outras versões de alimentadores não tinha. Nomeadamente, uma solução para recuperação de alimentação após queda de energia e resolver o problema de ração ficando presa no mecanismo de dispensa. Minha área também sofre com quedas de energia aleatórias. As quedas nunca duram muito, então não vi necessidade de redundância de energia. Também queria algo fácil de desmontar e limpar. Cuidado ao lavar as peças impressas em 3D, pode usar água morna, NÃO água quente a menos que planeje imprimir com ABS ou PET-G.

A lista completa de recursos é:

Demonstração

Vídeo de demonstração em breve!

O que mais você precisa?

As Peças Impressas em 3D

Recentemente adquiri uma impressora 3D e pensei, que melhor maneira de aprender e colocá-la em uso do que imprimir peças personalizadas para meu alimentador de pet. Tudo impresso em PLA+ e foi impresso usando uma Ender 3 Pro com Cura como fatiador. Os STLs para todas as peças podem ser encontrados na página do projeto no Github. Link abaixo. Leia o arquivo README para instruções de impressão e configurações do fatiador.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

A Caixa

A caixa foi projetada com conveniência e simplicidade em mente. Sem parafusos ou porcas necessários. Basta encaixar os componentes para dentro e para fora. Cada inserção tem 4 abas que mantêm os componentes no lugar.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Embora eu tenha errado inicialmente o slot da tela LCD, voltei e corrigi o modelo no Fusion 360. Apenas fiquei com preguiça de reimprimir. Usei alguns parafusos M3 0.5 x 6mm extras que tinha para fixá-lo. Há quatro espaçadores em cada canto do slot do LCD com furos para que você possa prender a tela se necessário. Infelizmente, nunca tirei fotos da tampa da caixa antes de colocar tudo dentro.

O Anexos da Roda Codificadora

A roda codificadora serve a dois propósitos:

Uma porção de alimentação equivale a 1/6 (60 graus) de volta da roda. Use uma balança de alimentos para medir a quantidade de ração que seu pet recebe por alimentação e então ajuste o número de porções até obter uma faixa que satisfaça essa quantidade. Acredito que uma alimentação para mim era cerca de 173 gramas de ração. Um tamanho de porção de 17 me deu uma faixa de 170-177 gramas por alimentação. Tudo depende do tamanho do seu grão. Certifique-se de que o cubo esteja situado entre as 8 porcas sextavadas M3.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

O Suporte do Servo e Montagem do Sensor Hall

Este suporte personalizado do servo também segura o sensor Hall e monta o servo em um pedaço de madeira 1-1/2 x 1-1/2 polegada (3.8 x 3.8 cm). O comprimento da madeira dependerá de onde o servo ficará (mais sobre isso depois). Há bastante margem de erro com o suporte, então não se preocupe muito em acertar o comprimento perfeito.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

O Funil, Chute de Ração e Trilho do Chute

Isso compõe o sistema de entrega de ração. A ração vem do dispensador através do funil e cai no chute e na tigela. Infelizmente, não tirei fotos do trilho do chute antes de montá-lo.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

A Estrutura

*Aviso* Não tirei fotos de todas as etapas do processo de montagem. Algumas dessas fotos terão etapas puladas, mas ainda assim vou guiá-lo passo a passo para a montagem da estrutura.

Certifique-se de pré-furar furos para qualquer parafuso que usar. Você NÃO quer rachar a madeira!

Este é o dispensador de cereal que comprei na Amazon. Existem alguns que têm múltiplos dispensadores se você tem vários pets e precisa de múltiplos alimentadores, eu só precisava de um. A marca é Honey Can Do, mas tenho certeza que qualquer marca funciona.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

A primeira coisa que comecei foi remover o botão da haste que conectava à roda de pás no dispensador. Usei uma serra manual para remover o botão. Pode usar ferramenta elétrica se tiver. Não corte a haste mais abaixo onde a parte prateada termina. Corte bem na base do botão.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Depois de serrar o botão, remova o material restante cortando os 3 suportes vistos na última foto acima. Isso levou um bom tempo. Imagino que será mais rápido se você tiver a ferramenta elétrica certa. Uma vez removidos esses três suportes, a peça restante na própria haste deve sair com um pouco de esforço. Você terá que lixar a parte da haste mais próxima do botão para que caiba bem na roda codificadora.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Em seguida, começamos a construir a base da estrutura do alimentador. Peguei a madeira 1/2 x 6 polegadas (1.27 x 15.24 cm) e cortei em dois pedaços de 8 polegadas (20.32 cm). Isso comporá a base e o painel traseiro da estrutura do alimentador. Aplique cola de madeira e use 2 dos pregos multiuso para uní-los em forma de L. Você precisará adicionar alguns suportes de ângulo reto para reforçar a conexão do painel traseiro e do painel inferior. Use 4 parafusos multiuso e cola de madeira para fixá-los tanto no painel traseiro quanto no inferior. Não tenho foto dessa etapa, mas você pode vê-las nas fotos mais abaixo.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

A partir daqui você deve cortar o suporte do dispensador para uma altura de cerca de 4.5 polegadas (11.43 cm). Não precisa ser perfeito. Tente chegar o mais perto possível. O funil permite alguma folga. Uma vez que você tenha o suporte cortado no tamanho certo, coloque-o contra o painel traseiro e certifique-se de que está assentado plano na base. Uma vez posicionado, use lápis ou caneta para marcar o centro de onde a extremidade de dispensa do recipiente ficará. Você então precisará furar um furo de 2 polegadas (5.08 cm) completamente através do painel da base. É crítico que você meça duas vezes e corte uma vez neste caso. Esse será o furo visto na foto acima.

Depois de furar o furo no painel da base, estamos prontos para anexar o suporte do dispensador ao painel traseiro. O que você quer fazer é colocar o suporte contra o painel traseiro (mostrado na primeira foto abaixo). Há duas áreas de recuo sob o anel do suporte do dispensador. É onde você quer que os furos fiquem (veja foto abaixo). Use lápis ou caneta para marcar a altura onde os dois furos devem estar no painel traseiro. Você quer eles o mais próximo possível do centro do suporte do dispensador. Há dois parafusos que conectam o anel superior do suporte à parte inferior que você cortou. Cuidado para não acertar esses ao furar. Lembre-se de medir duas vezes e furar uma vez. Ou duas vezes, neste caso.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Agora que temos os furos furados, podemos anexar o suporte ao painel traseiro usando parafusos sextavados 5/16 polegada, porcas sextavadas 5/16 polegada e arruelas 5/16 polegada. Você quer empurrar os parafusos garantindo que coloque arruelas na extremidade sextavada dos parafusos antes de empurrá-los. Depois que saírem do outro lado, coloque o outro conjunto de arruelas no lado rosqueado e comece a apertar as porcas à mão. Isso vai ser um pouco complicado. Depois de apertar as porcas à mão, você terá que usar uma chave de soquete para segurar a porca e apertar mais. Apenas cerca de 1/4 de volta. Cuidado para não apertar demais.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Agora que o suporte está seguramente fixado, podemos adicionar o pedaço de madeira 1/2 x 1/2 polegada onde o servo ficará. O comprimento disso vai depender de onde seu servo ficará. Vá em frente e monte o mecanismo de alimentação anexando o servo ao cubo na roda codificadora e a roda codificadora à pá de borracha dentro do recipiente plástico. Coloque o recipiente no anel superior e meça onde o servo fica a partir do painel da base. Não se preocupe em ser perfeito. O suporte do servo permite bastante folga. Use cola de madeira e um único parafuso multiuso para fixar o suporte de madeira.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

O próximo passo é anexar as pernas ao painel da base do alimentador. O comprimento das pernas vai depender da altura da tigela de ração do seu pet. Minha cachorra tem a tigela em um suporte elevado; portanto, precisei que meu alimentador ficasse bem alto. Use 4 dos parafusos multiuso e cola de madeira para fixá-los no lugar. Recomendo colocar uma viga transversal entre as duas pernas da frente e duas de trás e outra viga transversal entre as vigas transversais como mostrado abaixo para maior estabilidade. Use um total de 6 parafusos multiuso e cola de madeira para fixar as peças nas pernas.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Os próximos passos são:

Anexe o servo ao suporte usando os 4 parafusos M3 x 10 mm e 4 das porcas sextavadas M3. Uma vez que o servo está seguro, podemos fixar o suporte no suporte de madeira do servo. Use dois dos parafusos multiuso para fixar levemente o suporte no suporte de madeira. Não aperte demais ou danificará o suporte. Certifique-se de fazer os passos nessa ordem. O funil vai levantar um pouco o servo e encaixa bem apertado contra a extremidade do recipiente plástico, então é impossível colocar se o recipiente plástico já estiver assentado no anel superior do suporte.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Os passos finais serão anexar o suporte deslizante do chute e o próprio deslizamento. Você quer posicionar o suporte ligeiramente atrás do furo no painel da base. Você quer o mais à frente possível para que o deslizamento limpe a estrutura do alimentador. Use dois dos parafusos multiuso para fixar o suporte na parte inferior do painel da base da estrutura. É melhor fazer isso com o deslizamento no suporte, pois o suporte tem alguma flexibilidade e você vai querer um encaixe bem seguro entre o deslizamento e o suporte.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

A Eletrônica

Infelizmente, nunca tirei fotos do processo de soldagem. Não tem muito segredo. Apenas solde cada componente aos seus pinos correspondentes e deve funcionar. Se preferir usar headers de pinos, também pode fazer assim. Há espaço suficiente sob e acima do slot do Arduino para permitir os pinos e conectores. Definitivamente recomendo soldar tudo antes de colocar todos os componentes em seus respectivos slots.

Alimentei meu Arduino através do pino de alimentação externa não regulado (pino 30). Isso requer uma tensão de entrada entre 7-20 volts porque essa tensão é alimentada pelo regulador onboard do Arduino. Se você quiser alimentar via USB, apenas certifique-se de que está fornecendo 5 Volts, NÃO 7-20 Volts.

Certifique-se de soldar o resistor de 10k Ohm entre os pinos Vcc e Sinal no sensor Hall. Caso contrário, você não obterá leitura. Além disso, não se esqueça de conectar todos os componentes ao ground comum. Cometi o erro de perder um dos grounds e meu sistema funcionava por um tempo, mas o sensor Hall eventualmente começava a falhar. Foi um dia muito bom para meu cachorro.

Acabei fazendo conectores personalizados para as conexões do sensor Hall e servo. Soldei os fios em headers de pinos macho. Estes ficam saindo da parte inferior da caixa. Para o sensor Hall, fiz um adaptador fêmea personalizado cortando, descascando e soldando alguns fios conector Dupont que tinha por aí.

Para o trilho de Vcc e ground, cortei a seção do trilho de energia de uma protoboard perma-proto extra que tinha. Qualquer coisa similar que você tiver funcionará. Apenas verifique suas conexões antes de colocar o trilho no slot. Esta é a coisa mais difícil de tirar uma vez que está dentro. Também aprendi da pior forma.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

É isso! Espero que vocês se divirtam tanto construindo isso quanto eu. Parece desafiador no começo, mas quanto mais você se aprofunda, mais fácil fica. Me avise se tiver alguma dúvida sobre qualquer coisa. Feliz mexer!

Código

Código do Alimentador

// Versão final para o alimentador de pet
/* Recursos:
- Menu fácil de navegar
- Visão geral dos horários de alimentação, hora atual, conclusões de alimentação e porção na tela principal
- Porções controláveis usando sensor Hall para feedback
- Controle de tempo preciso com chip DS1307
- Pode alterar manualmente hora definida no chip DS1307
- Duas alimentações por dia
- Opção de alimentação manual
- Reinício de alimentação em caso de queda de energia
- Indicação por LED de sensor Hall e relógio em tempo real
- Horários e conclusões de alimentação armazenados com segurança na EEPROM
- "Sacudida" do servo caso a ração trave durante a dispensa
*/
#include <JC_Button.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <LiquidMenu.h>
#include <RTClib.h>
#include <Servo.h>
#include <EEPROM.h>

// Cria objeto servo para controlar seu servo
Servo myservo;

// Atribuindo todos os meus pinos de entrada e I/O
#define ENTER_BUTTON_PIN 11
#define UP_BUTTON_PIN 10
#define DOWN_BUTTON_PIN 9
#define BACK_BUTTON_PIN 8
#define POWER_LED_PIN 5
#define MANUAL_BUTTON_PIN A1
#define hallPin 2
#define HALL_LED_PIN 7
#define SERVO_PIN 6

// Definindo todos os Botões, funciona com a biblioteca JC_Button
Button enterBtn (ENTER_BUTTON_PIN);
Button upBtn (UP_BUTTON_PIN);
Button downBtn (DOWN_BUTTON_PIN);
Button backBtn (BACK_BUTTON_PIN);
Button manualFeedBtn (MANUAL_BUTTON_PIN);

// Definindo LCD I2C e RTC
LiquidCrystal_I2C lcd(0x27, 16, 2);
RTC_DS1307 rtc;

// Variáveis usadas ao longo do código
unsigned long 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 = 0;
boolean blink_state = false;
int count;
boolean feeding1_complete = false;
boolean feeding2_complete = false;
boolean feeding1_trigger = false;
boolean feeding2_trigger = false;
boolean servoOn = true;

// Interrupção do sensor Hall
volatile boolean hallSensorActivated = false;
volatile int isr_count = 1;

void hallActiveISR() {
  hallSensorActivated = true;
  digitalWrite(HALL_LED_PIN, HIGH);
  isr_count = isr_count + 1;
}

/* Uso enums aqui para manter melhor controle de qual botão está sendo pressionado
em vez de apenas ter cada botão definido como um valor inteiro. */
enum { btnENTER, btnUP, btnDOWN, btnBACK };

/* Estados da Máquina de Estados. Mesma coisa aqui com o tipo enum.
Facilita acompanhar em qual menu você está ou quer ir em vez de dar a cada menu um valor inteiro */
enum STATES {
  MAIN,
  MENU_EDIT_FEED1,
  MENU_EDIT_FEED2,
  MENU_EDIT_TIME,
  MENU_EDIT_PORTION,
  EDIT_FEED1_HOUR,
  EDIT_FEED1_MIN,
  EDIT_FEED2_HOUR,
  EDIT_FEED2_MIN,
  EDIT_HOUR,
  EDIT_MIN,
  EDIT_PORTION
};

// Mantém estado da máquina de estados
STATES state;

// Variáveis de entrada do usuário
int Hour;
int Minute;
int portion;
int feed_time1_hour;
int feed_time1_min;
int feed_time2_hour;
int feed_time2_min;
int userInput;

// Caractere especial de marca de verificação
byte check_Char[8] = {
  B00000, B00000, B00001, B00011,
  B10110, B11100, B11000, B00000
};

//======================O Setup===========================
void setup() {
  Wire.begin();
  Serial.begin(9600);
  lcd.init();
  lcd.backlight();
  lcd.createChar(0, check_Char);
  if (!rtc.begin()) {
    Serial.println("Não encontrou RTC!");
  }
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // Os botões
  enterBtn.begin();
  upBtn.begin();
  downBtn.begin();
  backBtn.begin();
  manualFeedBtn.begin();
  // Configurando estado inicial da Máquina de Estados
  state = MAIN;
  // Configurando entradas e saídas
  pinMode(POWER_LED_PIN, OUTPUT);
  pinMode(HALL_LED_PIN, OUTPUT);
  pinMode(hallPin, INPUT);
  /* Anexa interrupção ao pino que conecta ao sensor Hall.
  O sensor Hall que usei é definido como HIGH e vai LOW quando encontra um ímã.
  Por isso está definido como FALLING */
  attachInterrupt (digitalPinToInterrupt(hallPin), hallActiveISR, FALLING);
  // estado padrão dos LEDs
  digitalWrite(POWER_LED_PIN, HIGH);
  digitalWrite (HALL_LED_PIN, LOW);
  /* Estas funções obtêm o horário de alimentação armazenado, alimentação concluída,
  e tamanho da porção da EEPROM na inicialização. Fiz isso porque tenho quedas de energia
  aleatórias aqui onde moro. */
  get_feed_time1();
  get_feed_time2();
  get_completed_feedings();
  get_portion();
}

//======================O Loop===========================
void loop() {
  changing_states();
  check_buttons();
  check_feedtime ();
  check_rtc();
  manual_feed_check ();
}

//=============== As Funções =======================
/* Usa a biblioteca JC_Button para verificar continuamente se um botão foi pressionado.
Dependendo de qual botão é pressionado, define a variável userInput para ser usada na
função menu_transitions */
void check_buttons () {
  enterBtn.read();
  upBtn.read();
  downBtn.read();
  backBtn.read();
  manualFeedBtn.read();
  if (enterBtn.wasPressed()) {
    Serial.println ("Você pressionou Enter!");
    userInput = btnENTER;
    menu_transitions(userInput);
  }
  if (upBtn.wasPressed()) {
    Serial.println ("Você pressionou Cima!");
    userInput = btnUP;
    menu_transitions(userInput);
  }
  if (downBtn.wasPressed()) {
    Serial.println ("Você pressionou Baixo!");
    userInput = btnDOWN;
    menu_transitions(userInput);
  }
  if (backBtn.wasPressed()) {
    Serial.println ("Você pressionou Voltar!");
    userInput = btnBACK;
    menu_transitions(userInput);
  }
  if (manualFeedBtn.wasPressed()) {
    Serial.println ("Você está alimentando manualmente!");
    manualFeed = true;
  }
}

//=====================================================
/* Esta função determina o que é exibido, dependendo de qual menu ou "estado" você está.
Cada menu tem uma função que exibe o respectivo menu */
void changing_states() {
  switch (state) {
    case MAIN:
      display_current_time();
      display_feeding_times();
      display_portion();
      break;
    case MENU_EDIT_FEED1:
      display_set_feed_time1_menu();
      break;
    case MENU_EDIT_FEED2:
      display_set_feed_time2_menu();
      break;
    case MENU_EDIT_TIME:
      display_set_time_menu();
      break;
    case MENU_EDIT_PORTION:
      display_set_portion_menu ();
      break;
    case EDIT_FEED1_HOUR:
      set_feed_time1();
      break;
    case EDIT_FEED1_MIN:
      set_feed_time1();
      break;
    case EDIT_FEED2_HOUR:
      set_feed_time2();
      break;
    case EDIT_FEED2_MIN:
      set_feed_time2();
      break;
    case EDIT_HOUR:
      set_the_time();
      break;
    case EDIT_MIN:
      set_the_time();
      break;
    case EDIT_PORTION:
      set_the_portion();
      break;
  }
}

//=====================================================
/* Esta é a parte de transição da máquina de estados.
É o que permite ir de um menu para outro e alterar valores */
void menu_transitions(int input) {
  switch (state) {
    case MAIN:
      if (input == btnENTER) {
        lcd.clear();
        state = MENU_EDIT_FEED1;
      }
      if (input == btnBACK) {
        hall_sensor_fail = false;
      }
      break;
    //----------------------------------------------------
    case MENU_EDIT_FEED1:
      if (input == btnBACK) {
        lcd.clear();
        state = MAIN;
      } else if (input == btnENTER) {
        lcd.clear();
        state = EDIT_FEED1_HOUR;
      } else if (input == btnDOWN) {
        lcd.clear();
        state = MENU_EDIT_FEED2;
      }
      break;
    //----------------------------------------------------
    case EDIT_FEED1_HOUR:
      // Precisa disso para evitar servo ligando enquanto define hora
      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( "*Configurações Salvas*");
        delay(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( "*Configurações Salvas*");
        delay(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( "*Configurações Salvas*");
        delay(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( "*Configurações Salvas*");
        delay(1000);
        lcd.clear();
        write_portion();
        state = MAIN;
      }
      break;
  }
}

//=====================================================
// Esta função verifica o horário de alimentação contra a hora atual
void 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 ("Dispensando");
          lcd.setCursor(1, 1);
          lcd.print("Primeira Alimentação");
          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 ("Dispensando");
          lcd.setCursor(0, 1);
          lcd.print("Segunda Alimentação");
          startFeeding();
        }
      }
    }
  }
  // Reset à meia-noite
  if ( (now.hour() == 0) && (now.minute() == 0)) {
    feeding1_complete = false;
    feeding2_complete = false;
    EEPROM.write(4, feeding1_complete);
    EEPROM.write(5, feeding2_complete);
  }
  /* Se queda de energia acontece durante horário de alimentação, isso verifica se o horário
  passou e se a alimentação ocorreu. Se não, alimenta. */
  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 ("Ops!");
      lcd.setCursor(2, 1);
      lcd.print("Queda de Energia");
      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 ("Ops!");
      lcd.setCursor(2, 1);
      lcd.print("Queda de Energia");
      startFeeding();
    }
  }
}

//=====================================================
// Exibe a opção de menu para definir a porção
void display_set_portion_menu () {
  lcd.setCursor(2, 0);
  lcd.print("Opções do Menu");
  lcd.setCursor(0, 1);
  lcd.print("Definir a Porção");
}

//=====================================================
// Exibe o menu onde você altera a hora atual
void set_the_time () {
  lcd.setCursor(2, 0);
  lcd.print("Definir a Hora");
  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);
      break;
    //----------------------------------------------------
    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();
}

//=====================================================
// Exibe o menu onde você altera a porção de alimentação
void set_the_portion () {
  lcd.setCursor (0, 0);
  lcd.print("Definir a Porção");
  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();
}

//=====================================================
// Exibe a opção de menu para definir a hora
void display_set_time_menu () {
  lcd.setCursor(2, 0);
  lcd.print("Opções do Menu");
  lcd.setCursor(2, 1);
  lcd.print("Definir a Hora");
}

//=====================================================
// Exibe o menu onde você altera o segundo horário de alimentação
void set_feed_time2 () {
  lcd.setCursor(0, 0);
  lcd.print("Definir Horário Alim. 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);
      break;
    //----------------------------------------------------
    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();
}

//=====================================================
// Exibe o menu onde você altera o primeiro horário de alimentação
void set_feed_time1 () {
  lcd.setCursor(0, 0);
  lcd.print("Definir Horário Alim. 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);
      break;
    //----------------------------------------------------
    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();
}

//=====================================================
// Adiciona zero à esquerda para números de um dígito
void add_leading_zero (int num) {
  if (num < 10) {
    lcd.print("0");
  }
  lcd.print(num);
}

//=====================================================
/* Exibe o horário de alimentação no menu principal assim como a marca de verificação
para confirmação visual de alimentação concluída */
void display_feeding_times () {
  // Exibindo primeiro horário de alimentação
  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(" ");
  }
  // Exibindo segundo horário de alimentação
  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(" ");
  }
}

//=====================================================
// Exibe a hora atual no menu principal
void display_current_time () {
  DateTime now = rtc.now();
  lcd.setCursor(11, 0);
  add_leading_zero(now.hour());
  lcd.print(":");
  add_leading_zero(now.minute());
}

//=====================================================
// Exibe a opção de menu para definir o primeiro horário de alimentação
void display_set_feed_time1_menu () {
  lcd.setCursor(2, 0);
  lcd.print("Opções do Menu");
  lcd.setCursor(0, 1);
  lcd.print("Definir Horário Alim. 1");
}

//=====================================================
// Exibe a opção de menu para definir o segundo horário de alimentação
void display_set_feed_time2_menu () {
  lcd.setCursor(2, 0);
  lcd.print("Opções do Menu");
  lcd.setCursor(0, 1);
  lcd.print("Definir Horário Alim. 2");
}

//=====================================================
// Exibe a porção de alimentação no menu principal
void display_portion () {
  lcd.setCursor (12, 1);
  lcd.print("P:");
  add_leading_zero(portion);
}

//=====================================================
// Inicia o processo de alimentação.
void startFeeding() {
  // anexa o servo ao pino
  myservo.attach(SERVO_PIN);
  count = 1;
  hallSensorTime = millis();
  // loop para que o servo rode até a porção desejada ser atingida
  while (count <= portion) {
    servoStart();
    if (hallSensorActivated == true) {
      count = count + 1;
      // resetando para próxima interrupção
      hallSensorTime = millis();
      hallSensorActivated = false;
      digitalWrite(HALL_LED_PIN, LOW);
    }
    /* Move o servo no sentido horário um pouco para desalojar ração presa
    no mecanismo de dispensa */
    if (millis() - hallSensorTime > rotationTime) {
      hall_sensor_fail = true;
      Serial.println("Estou no Jiggle");
      jiggle();
    }
  }
  // Acompanha qual alimentação acabou de acontecer e escreve na 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);
  /* Desanexa o servo do pino para que não receba mais sinal.
  Pode ser necessário adicionar um delay antes disso para o sensor parar
  quando um ímã estiver sobre o sensor Hall. Havia momentum significativo
  no meu sistema que não precisei disso */
  myservo.detach();
  lcd.clear();
  delay_currentMillis = millis();
  while (millis() - delay_currentMillis <= delay_interval) {
    lcd.setCursor(2, 0);
    lcd.print ("Alimentação Concluída");
  }
  lcd.clear();
}

//=====================================================
void servoStart() {
  myservo.write(180);
}

//=====================================================
void servoStop() {
  // este valor varia, você tem que encontrar por tentativa e erro
  myservo.write(94);
}

//=====================================================
// "Sacode" o servo caso a ração trave
void jiggle() {
  myservo.write(80);
  delay(30);
  myservo.write(93);
  delay(30);
  myservo.write(180);
}

//=====================================================
// Escreve os valores de hora e minuto definidos para 1ª alimentação na EEPROM
void write_feeding_time1 () {
  EEPROM.write(0, feed_time1_hour);
  EEPROM.write(1, feed_time1_min);
}

//=====================================================
// Escreve os valores de hora e minuto definidos para 2ª alimentação na EEPROM
void write_feeding_time2 () {
  EEPROM.write(2, feed_time2_hour);
  EEPROM.write(3, feed_time2_min);
}

//=====================================================
// Escreve valor da porção definido na EEPROM
void write_portion () {
  EEPROM.write(6, portion);
}

//=====================================================
// Lê os valores de hora e minuto do 1º horário de alimentação da EEPROM
void 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;
}

//=====================================================
// Lê os valores de hora e minuto do 2º horário de alimentação da EEPROM
void 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;
}

//=====================================================
// Lê valor da porção definida da EEPROM
void get_portion () {
  portion = EEPROM.read(6);
}

//=====================================================
// Lê valor booleano de se as alimentações ocorreram da EEPROM
void get_completed_feedings() {
  feeding1_complete = EEPROM.read(4);
  feeding2_complete = EEPROM.read(5);
}

//=====================================================
/* Verifica se o sensor Hall falhou ao ler um ímã e pisca o LED vermelho */
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;
  }
}

//=====================================================
// Verifica se você pressiona o botão de alimentação manual
void manual_feed_check () {
  if (manualFeed == true) {
    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print(" Alimentação Manual");
    startFeeding();
    manualFeed = false;
  }
}

//=====================================================
// Verifica se RTC está rodando
void check_rtc () {
  if (!rtc.isrunning()) {
    led_blink();
  }
}

//=====================================================
/* Pisca o LED vermelho quando RTC falhou. Nota: o LED piscará
em taxa diferente de quando o sensor Hall falha */
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);
  }
}

//=====================================================
// Cria efeito de piscar ao alterar valores
void blinkFunction() {
  blink_currentMillis = millis();
  if (blink_currentMillis - blink_previousMillis > blink_interval) {
    blink_previousMillis = blink_currentMillis;
    blink_state = !blink_state;
  }
}

//=====================================================

Link para o Código no Meu Github

https://github.com/russo08/Pet-feeder/blob/main/feeder_final.ino

Peças Personalizadas e Caixas

Arquivos Fusion 360 e STL no Meu Github

Aqui estão todos os arquivos Fusion 360 caso você queira personalizá-los para diferentes tamanhos de componentes. Também forneci os arquivos STL. O único modelo que não está lá é o tubo para o sensor Hall. Isso deve ser bem fácil de modelar e imprimir.

https://github.com/russo08/Pet-feeder.git

Esquemáticos

Este é o esquema do circuito. Você pode modificá-lo se precisar. Se fizer, lembre-se de fazer os mesmos ajustes no código.

Alimentador Inteligente para Pets Impresso em 3D com Arduino Nano

Processo de manufatura

  1. Colorindo Peças Impressas em 3D com Giz de Cera: Uma Nova Técnica de Pós-Processamento
  2. Sensor de Rastreamento de Linha com Raspberry Pi – Detector KY‑033 Simples para Porta/Linha
  3. Construa API Robusta de Temperatura e Sensores no Raspberry Pi com Módulo GY‑91
  4. Integrando Sensor Termopar com Arduino Portenta H7 usando CI MAX6675
  5. Elevando o Controle de Qualidade: Ferramentas Impressas em 3D Aumentam a Precisão na Manufatura
  6. Projetando Adaptadores Funcionais de Alforje de Bicicleta Impressos em 3D com Kevlar CFF
  7. Reimaginando o Kart: Peças de Fibra de Carbono Impressas em 3D para Desempenho Inigualável
  8. Webinar: Construa Peças 3D Mais Fortes com Software Eiger
  9. Drone Impresso em 3D Permite Coleta Rápida de Dados de Baixo Custo na Antártida
  10. Como a Impressão 3D Deu Vantagem Competitiva à Equipe MIT Motorsports no Formula SAE