산업 제조
산업용 사물 인터넷 | 산업자재 | 장비 유지 보수 및 수리 | 산업 프로그래밍 |
home  MfgRobots >> 산업 제조 >  >> Manufacturing Technology >> 제조공정

3D 프린팅 부품이 있는 애완동물 사료 공급기

구성품 및 소모품

Arduino Nano R3
× 1
DS 1307 실시간 시계 모듈
모든 브랜드의 RTC를 사용할 수 있습니다. 더 저렴한 옵션이 있다고 확신합니다.
× 1
LM-317 전압 조정기 모듈
× 1
연속 회전 서보
× 1
시리얼 디스펜서
× 1
8mm 자석
× 6
홀 센서(단극)
얻는 센서가 UNIPOLAR인지 확인하여 자석의 역 극성이 필요하지 않도록 센서 출력 재설정
× 1
Adafruit RGB 백라이트 LCD - 16x2
× 1
I2C LCD 백팩
원하는 경우 배낭이 이미 부착된 LCD 화면을 구입할 수 있습니다.
× 1
5mm LED:빨간색
× 1
5mm LED:녹색
× 1
푸시버튼 스위치, 순간
× 5
암컷 DC 전원 잭 어댑터
× 1
9-12볼트 벽 사마귀
× 1
저항 10k 옴
홀 센서용. Vcc와 신호 핀 사이에 연결
× 1
저항 330옴
LED용. 너무 밝은 것을 원하지 않으면 더 높은 저항을 사용할 수 있습니다.
× 2

필요한 도구 및 기계

3D 프린터(일반)
드릴/드라이버, 무선
원형 톱

이 프로젝트 정보

애완 동물 사료 공급기의 비하인드 스토리

나는 잠시 동안 마이크로컨트롤러를 가지고 놀았고 튜토리얼에서 분기하려고 했습니다. 코로나19 이전에는 사실 정해진 일정이 없었고 꽤 오랜 시간 일했습니다. 우리 강아지의 수유 일정이 정말 힘들어지기 시작했고 눈에 띄는 불편함을 겪기 시작했습니다. 우리 지역은 허리케인 시즌에도 홍수가 발생하기 쉽습니다. 불행히도 집에 돌아가서 늙은 딸에게 먹일 수 없는 경우가 몇 번 있었습니다. 집에 갈 수 없어도 개가 배고프지 않도록 해결책을 찾아야 했습니다. $30-$40에 구입하는 대신 $100 이상에 구입하지 않으시겠습니까? 농담이야.

피더!

내 개 사료 공급기는 Arduino 마이크로 컨트롤러를 사용합니다. 대부분의 다른 개 모이통 버전에는 없는 몇 가지 핵심 기능이 필요했습니다. 즉, 정전 복구 급전 및 분배 메커니즘에 음식이 끼는 문제를 해결하는 솔루션입니다. 우리 지역은 또한 무작위 정전으로 고통 받고 있습니다. 정전은 일반적으로 오래 지속되지 않으므로 전원 이중화가 필요하지 않습니다. 나는 또한 쉽게 분해하고 청소할 수 있는 것을 원했습니다. 3D 프린팅된 부품을 세척할 때 주의하십시오. ABS 또는 PET-G로 프린팅할 계획이 아니라면 뜨거운 물이 아닌 따뜻한 물을 사용할 수 있습니다.

전체 기능 목록은 다음과 같습니다.

<울>
  • 하루 두 번 먹이기
  • <울>
  • 실시간 시계로 정확한 시간 표시
  • <울>
  • 실시간 시계의 수동 시간 변경
  • <울>
  • 수동 급지 옵션
  • <울>
  • 홀 센서 및 실시간 시계 오류의 LED 표시
  • <울>
  • 메인 화면의 수유 시간, 현재 시간 및 수유 완료 개요
  • <울>
  • 손쉬운 메뉴 탐색
  • <울>
  • 정전 급전 재개(전원이 다시 켜질 때 급전됨)
  • <울>
  • EEPROM에 안전하게 저장된 수유 시간 및 완료 횟수
  • <울>
  • 배출 중 음식이 걸린 경우 서보 "흔들림"
  • 데모

    데모 비디오가 곧 제공됩니다!

    그 밖에 무엇이 필요합니까?

    <울>
  • 4x M3-0.5 x 16mm 나사(인코더 휠)
  • <울>
  • 4x M3-0.5 x 10mm 나사(서보 브래킷)
  • <울>
  • 12x M3 육각 너트
  • <울>
  • 4x M3 잠금 너트
  • <울>
  • 16x 1-1/2인치(38mm) 다목적 나무 나사
  • <울>
  • 목재 풀
  • <울>
  • 0.315 x 0.118인치(8 x 3mm) 자석 6개
  • <울>
  • 다리 및 서보 마운트용 1-1/2 x 1-1/2인치(3.8 x 3.8cm) 목재
  • <울>
  • 1/2 x 6인치(1.27 x 15.24cm) 등판 및 베이스용 목재
  • 3D 프린팅 부품

    저는 최근에 3D 프린터를 구입하고 애완 동물 사료용 맞춤형 부품을 인쇄하는 것보다 배우고 사용하는 것이 더 좋은 방법이라고 생각했습니다. 인쇄된 모든 것은 PLA+이며 Cura를 슬라이서로 사용하는 Ender 3 Pro를 사용하여 인쇄되었습니다. 모든 부분에 대한 STL은 Github 프로젝트 페이지에서 찾을 수 있습니다. 아래로 링크합니다. 인쇄 지침 및 슬라이서 설정에 대한 README 파일을 읽으십시오.

    주택

    하우징은 편리함과 단순함을 염두에 두고 설계되었습니다. 나사나 너트가 필요하지 않습니다. 구성 요소를 넣고 빼기만 하면 됩니다. 각 삽입물에는 구성 요소를 제자리에 고정하는 4개의 탭이 있습니다.

    처음에는 LCD 화면 슬롯을 엉망으로 만들었지만 다시 돌아가서 Fusion 360에서 모델을 수정했습니다. 돌아가서 다시 인쇄하기에는 너무 게으른 것이었습니다. 나는 그것을 고정하기 위해 주위에 누워 있던 여분의 M3 0.5 x 6mm 나사를 사용했습니다. LCD 슬롯의 각 모서리에는 구멍이 있는 4개의 스페이서가 있어 필요한 경우 화면을 고정할 수 있습니다. 불행히도, 나는 모든 것을 넣기 전에 인클로저 덮개의 사진을 찍은 적이 없습니다.

    인코더 휠 부착

    휠 인코더 휠은 두 가지 용도로 사용됩니다.

    <울>
  • Arduino에 서보의 위치 피드백 제공
  • <울>
  • 고무 패들을 서보에 부착
  • 한 공급 부분은 바퀴의 1/6(60도) 회전과 같습니다. 사료 저울을 사용하여 반려동물이 한 번 수유하는 사료의 양을 측정한 다음 해당 양을 만족하는 범위가 될 때까지 부분 수를 조정하십시오. 나는 나를 위해 한 번의 먹이가 약 173g의 음식이었다고 생각합니다. 17인분의 양은 한 끼에 170-177g의 범위를 제공했습니다. 그것은 모두 사료의 크기에 달려 있습니다. 혼이 8개의 M3 육각 너트 사이에 있는지 확인하십시오.

    서보 브래킷 및 홀 센서 마운트

    이 맞춤형 서보 브래킷은 홀 센서도 고정하고 서보를 1-1/2 x 1-1/2인치(3.8 x 3.8cm) 나무 조각에 장착합니다. 나무의 길이는 서보가 앉는 위치에 따라 달라집니다(나중에 자세히 설명). 마운트에 오류의 여지가 충분히 있으므로 길이를 완벽하게 맞추는 것에 대해 너무 걱정하지 마십시오.

    깔때기, 식품 슈트 및 슈트 레일

    이것은 음식 배달 시스템을 구성합니다. 음식은 깔때기를 통해 디스펜서에서 내려와 슈트와 음식 그릇으로 들어갑니다. 아쉽게도 슈트레일을 장착하기 전 사진을 찍지 못했습니다.

    프레임

    *면책 조항* 조립 과정의 모든 단계를 사진에 담지 못했습니다. 이 사진 중 일부는 단계를 건너뛰었지만 여전히 프레임 조립에 대해 단계별로 안내하겠습니다.

    사용하는 나사의 구멍을 미리 뚫어야 합니다.나무를 쪼개고 싶으세요!

    아마존에서 구매한 시리얼 디스펜서입니다. 여러 마리의 애완 동물이 있고 여러 개의 피더가 필요한 경우 여러 디스펜서가있는 일부가 있습니다. 나는 하나만 필요했습니다. 브랜드는 Honey Can Do이지만 어떤 브랜드라도 효과가 있을 것이라고 확신합니다.

    내가 시작한 첫 번째 일은 디스펜서의 패들 휠에 연결된 막대에서 손잡이를 제거하는 것이었습니다. 손잡이를 제거하기 위해 톱을 사용했습니다. 전동 공구가 있으면 사용할 수 있습니다. 은색 부분이 끝나는 곳에서 막대를 더 아래로 자르지 마십시오. 손잡이 바닥에서 오른쪽을 자릅니다.

    손잡이를 자른 후 위의 마지막 사진에 보이는 3개의 지지대를 절단하여 나머지 재료를 제거합니다. 꽤 오래 걸렸습니다. 올바른 전동 공구가 있으면 더 빠를 것이라고 생각합니다. 3개의 지지대를 제거하면 막대 자체에 남아 있는 나머지 부분이 약간의 팔꿈치 그리스와 함께 떨어져야 합니다. 엔코더 휠에 잘 맞도록 손잡이에 가장 가까운 막대 부분을 연마해야 합니다.

    다음으로 프레임의 베이스를 만들기 시작합니다. 나는 1/2 x 6인치(1.27 x 15.24cm) 나무를 2개의 8인치(20.32cm) 조각으로 자릅니다. 이것은 피더 프레임의 베이스와 뒷면을 구성합니다. 목공풀을 바르고 다용도 못 2개를 사용하여 L자 모양으로 연결합니다. 후면 패널과 하단 패널 연결을 강화하려면 두 개의 직각 지지대를 추가해야 합니다. 4개의 다목적 나사와 약간의 목공풀을 사용하여 후면 및 하단 패널에 고정합니다. 이 단계의 사진은 없지만 아래 사진에서 볼 수 있습니다.

    여기에서 디스펜서 스탠드를 약 4.5인치(11.43cm) 높이로 잘라야 합니다. 완벽할 필요는 없습니다. 가능한 한 가깝게 하려고 합니다. 깔때기는 약간의 흔들림 공간을 허용합니다. 크기에 맞게 스탠드를 자른 후에는 등받이에 대고 받침대에 평평하게 놓였는지 확인하십시오. 제 위치에 놓으면 연필이나 펜을 사용하여 용기의 분배 끝이 위치할 중심을 표시하십시오. 그런 다음 베이스 패널 전체에 2인치(5.08cm) 구멍을 뚫어야 합니다. 두 번 측정하고 이것으로 한 번 자르는 것이 중요합니다. 위 사진에 보이는 구멍이 됩니다.

    베이스 패널에 구멍을 뚫으면 디스펜서 스탠드를 후면 패널에 부착할 준비가 된 것입니다. 당신이하고 싶은 것은 후면 패널에 대해 스탠드를 설정하는 것입니다 (아래 첫 번째 그림 참조). 디스펜서 스탠드의 링 아래에 두 개의 작은 공간이 있습니다. 이것은 구멍을 원하는 곳입니다(아래 그림 참조). 연필이나 펜을 사용하여 후면 패널에서 두 개의 구멍이 있어야 하는 높이를 표시합니다. 가능한 한 디스펜서 스탠드 중앙에 가깝게 설치하는 것이 좋습니다. 스탠드의 상단 링을 잘라낸 하단 부분에 연결하는 두 개의 나사가 있습니다. 드릴링할 때 부딪히지 않도록 주의하십시오. 다시 한 번, 두 번 측정하고 한 번 드릴하는 것을 잊지 마십시오. 또는 이 경우에는 두 번.

    이제 구멍을 뚫었으므로 5/16인치 육각 볼트, 5/16인치 육각 너트 및 5/16인치 와셔를 사용하여 후면 패널에 스탠드를 부착할 수 있습니다. 계속 진행하고 볼트를 밀기 전에 볼트의 육각 끝에 와셔를 배치했는지 확인하십시오. 다른 쪽에서 나온 후 다른 세트의 와셔를 나사쪽에 놓고 육각 너트를 손으로 조이기 시작합니다. 이것은 약간 까다로울 것입니다. 너트를 손으로 조인 후 소켓을 사용하여 너트를 잡고 더 조여야 합니다. 약 1/4 정도만 회전합니다. 과도하게 조이지 않도록 주의하십시오.

    이제 스탠드가 안전하게 고정되었으므로 이제 서보가 앉을 나무 조각을 1/2 x 1/2인치 추가할 수 있습니다. 이 길이는 서보가 앉을 위치에 따라 달라집니다. 인코더 휠의 혼에 서보를 부착하고 플라스틱 용기 내부의 고무 패들에 인코더 휠을 부착하여 공급 장치를 조립합니다. 컨테이너를 상단 링에 내려 놓고 베이스 패널에서 서보가 위치하는 위치를 측정합니다. 완벽하다고 걱정하지 마십시오. 서보 브래킷은 약간의 흔들림 공간을 허용합니다. 목제 풀과 다용도 나사 1개를 사용하여 목제 받침대를 고정합니다.

    다음 단계는 다리를 피더의 베이스 패널에 부착하는 것입니다. 다리의 길이는 애완 동물의 음식 그릇의 높이에 따라 다릅니다. 내 개는 높은 스탠드에 음식 그릇을 가지고 있습니다. 그러므로 나는 피더가 꽤 높은 곳에 앉을 필요가 있었습니다. 다용도 나사 4개와 목공풀을 사용하여 제자리에 고정합니다. 안정성을 더하기 위해 아래와 같이 두 개의 전면 다리와 두 개의 후면 다리 사이에 가로대를 놓고 가로대 사이에 다른 가로대를 배치하는 것이 좋습니다. 총 6개의 다용도 나사와 목공풀을 사용하여 조각을 다리에 고정하세요.

    다음 단계는 다음과 같습니다.

    <울>
  • 베이스 패널에 뚫은 구멍에 깔때기를 삽입합니다.
  • <울>
  • 서보 브래킷에 서보 부착
  • <울>
  • 나무 받침대에 브래킷 고정
  • 4개의 M3 x 10mm 나사와 4개의 M3 육각 너트를 사용하여 서보를 브래킷에 부착합니다. 서보가 고정되면 브래킷을 나무 서보 스탠드에 고정할 수 있습니다. 다목적 나사 2개를 사용하여 브래킷을 목재 스탠드에 가볍게 고정합니다. 과도하게 조이지 마십시오. 브래킷이 손상될 수 있습니다. 순서대로 단계를 수행하십시오. 깔때기는 서보를 약간 올릴 것이고 플라스틱 용기의 끝에 아주 꼭 맞기 때문에 플라스틱 용기가 이미 상단 링 스탠드에 있는 경우 끼울 수 없습니다.

    마지막 단계는 슈트의 슬라이드 인 브래킷과 슬라이드 자체를 부착하는 것입니다. 베이스 패널의 구멍 약간 뒤에 브래킷을 배치하려고 합니다. 슬라이드가 피더의 프레임을 제거할 수 있도록 최대한 앞으로 밀어야 합니다. 다목적 나사 2개를 사용하여 브래킷을 프레임의 베이스 패널 바닥에 고정합니다. 브래킷에 약간의 유연성이 있고 슬라이드와 브래킷 사이에 꽤 안전한 고정을 원할 것이기 때문에 브래킷의 슬라이드를 사용하여 이 작업을 수행하는 것이 가장 좋습니다.

    전자 제품

    불행히도, 나는 납땜 과정을 사진으로 본 적이 없습니다. 그러나 그것에는별로 없습니다. 각 구성 요소를 해당 핀에 납땜하기만 하면 됩니다. 핀 헤더를 대신 사용하려는 경우에도 그렇게 할 수 있습니다. 핀과 커넥터를 허용할 수 있도록 Arduino 슬롯 위와 아래에 충분한 여유 공간이 있습니다. 각 슬롯에 모든 구성 요소를 설정하기 전에 모든 것을 함께 납땜하는 것이 좋습니다.

    조정되지 않은 외부 전원 핀(핀 30)을 통해 Arduino에 전원을 공급했습니다. 이 전압은 Arduino의 온보드 레귤레이터를 통해 공급되기 때문에 7-20V 사이의 입력 전압이 필요합니다. USB를 통해 전원을 공급하려면 7-20볼트가 아닌 5볼트만 공급해야 합니다.

    홀 센서의 Vcc와 신호 핀 사이에 10k Ohm 저항을 납땜해야 합니다. 그렇지 않으면 읽을 수 없습니다. 또한 모든 구성 요소를 공통 접지에 연결하는 것을 잊지 마십시오. 나는 부지 중 하나를 놓치는 실수를 저질렀고 내 시스템은 잠시 동안 작동했지만 홀 센서는 결국 실패하기 시작했습니다. 강아지에게 정말 좋은 날이었습니다.

    결국 홀 센서와 서보 연결을 위한 맞춤형 커넥터를 만들었습니다. 전선을 수컷 핀 헤더에 납땜했습니다. 이들은 인클로저 바닥에 매달려 있습니다. 홀 센서의 경우 주변에 있던 Dupont 커넥터 와이어 몇 개를 자르고, 벗기고, 납땜하여 맞춤형 암 어댑터를 만들었습니다.

    Vcc 및 접지 레일의 경우 주변에 있던 추가 영구 브레드보드에서 전원 레일 섹션을 잘라냈습니다. 주변에 있는 유사한 모든 것이 작동합니다. 레일을 슬롯에 설치하기 전에 연결을 다시 확인하십시오. 이것은 한 번 들어가면 빠져 나오는 것이 가장 어렵습니다. 저도 그것을 어렵게 배웠습니다.

    그게 다야! 여러분도 저처럼 이 빌드를 재미있게 만드시기 바랍니다. 처음에는 어려워 보이지만 하면 할수록 쉬워집니다. 궁금한 점이 있으면 알려주세요. 즐거운 땜질!

    <섹션 클래스="섹션 컨테이너 섹션 축소 가능" id="코드">

    코드

    <울>
  • 피더 코드
  • 피더 코드C/C++
    // 반려동물 사료 공급기의 최종 버전/* 기능:- 간편한 메뉴 탐색 - 메인 화면에서 수유 시간, 현재 시간, 수유 완료 및 수유 부분의 개요 - 피드백을 위한 홀 센서를 사용하여 제어 가능한 부분 - 정확한 DS1307 칩으로 시간 유지 - DS1307 칩에서 수동으로 설정 시간 변경 가능 - 1일 2회 급지 - 수동 급지 옵션 - 정전 시 급지 재시작 - 홀 센서 및 실시간 시계의 LED 표시 - 급식 시간 및 완료는 EEPROM에 안전하게 저장 - 음식물이 끼었을 때 서보 "흔들림"*/#include #include #include #include #include # include #include // 서보 서보 제어를 위한 서보 객체 생성 myservo;// 모든 입력 및 I/O 핀 할당#define ENTER_BUTTON_PIN 11#define UP_BUTTON_PIN 10#define DOWN_BUTTON_PIN 9#define BACK_BUTTON 8#define POWER_LED_PIN 5#define MANUAL_BUTTON_PIN A1#define hallPin 2#define HALL_LE D_PIN 7#define SERVO_PIN 6// 모든 버튼 정의, JC_Button 라이브러리와 함께 작동Button enterBtn(ENTER_BUTTON_PIN);버튼 upBtn(UP_BUTTON_PIN);Button downBtn(DOWN_BUTTON_PIN);Button downBtn(DOWN_BUTTON_PIN);Feedton/Finton backBUTTON_PIN_BUTTON;BUTTON_PIN LCD I2C 및 RTCLiquidCrystal_I2C lcd(0x27, 16, 2);RTC_DS1307 rtc;//코드 전체에 사용된 변수unsigned long hallSensorTime;unsigned long rotationTime =400;unsigned long led_previousMillis =0; int ledState =LOW;boolean manualFeed =false;boolean hall_sensor_fail =false;unsigned long 깜박임_previousMillis =0;unsigned long 깜박임_currentMillis =0;unsigned long flash_interval =500;unsigned long delay_currentMillis =0;unsigned long delay_previous;Millian 정수 카운트; 부울 Feeding1_complete =false; 부울 Feeding2_complete =false; 부울 Feeding1_trigger =false; 부울 Feeding2_trigger =false; 부울 서보온 =tru e;//홀 센서 인터럽트 휘발성 부울 hallSensorActivated =false;volatile int isr_count =1;void hallActiveISR(){ hallSensorActivated =true; 디지털 쓰기(HALL_LED_PIN, 높음); isr_count =isr_count + 1;}/* 각 버튼을 정수 값으로 설정하는 것과는 대조적으로 어떤 버튼이 눌러졌는지 더 잘 추적하기 위해 여기에서 열거형을 사용합니다.*/enum { btnENTER, btnUP, btnDOWN, btnBACK,}; /* 상태 머신의 상태. enum 유형도 마찬가지입니다. 각 메뉴에 정수 값*/enum STATES { MAIN, MENU_EDIT_FEED1, MENU_EDIT_FEED2, MENU_EDIT_TIME, MENU_EDIT_PORTION, EDIT_FEED1_HOUR, EDIT_FEED1_HOUR, EDIT_FEED1_FITED_2, EDIT_FEED1_MINI를 지정하는 대신 현재 있거나 가고 싶은 메뉴를 쉽게 추적할 수 있습니다. , EDIT_MIN, EDIT_PORTION};// 상태 machineSTATES 상태 유지;//사용자 입력 변수int Hour;int Minute;int 부분;int feed_time1_hour;int feed_time1_min;int feed_time2_hour;int feed_time2_min;int userInput;// 특수 문자 확인 markbyte check_Char[8] ={ B00000, B00000, B00001, B00011, B10110, B11100, B11000, B00000};//=================================================무효 setup() { Wire.begin(); Serial.begin(9600); lcd.init(); lcd.backlight(); lcd.createChar(0, check_Char); if (!rtc.begin()) { Serial.println("RTC를 찾을 수 없습니다!"); } // rtc.adjust(날짜시간(F(__DATE__), F(__TIME__))); //버튼은 enterBtn.begin(); upBtn.begin(); downBtn.begin(); backBtn.begin(); manualFeedBtn.begin(); // State Machine 초기 상태 설정 state =MAIN; //입력 및 출력 설정 pinMode(POWER_LED_PIN, OUTPUT); 핀모드(HALL_LED_PIN, 출력); 핀모드(홀핀, 입력); /* 홀 센서에 연결되는 핀에 인터럽트를 연결합니다. 내가 사용한 홀 센서는 HIGH로 설정되어 있고 자석을 만나면 LOW가 됩니다. 이것이 FALLING으로 설정된 이유입니다. */ attachInterrupt (digitalPinToInterrupt(hallPin), hallActiveISR, FALLING); // LED의 기본 상태 digitalWrite(POWER_LED_PIN, HIGH); 디지털 쓰기(HALL_LED_PIN, LOW); /* 이 함수는 시작 시 EEPROM에서 저장된 공급 시간, 완료된 공급 및 부분 크기를 가져옵니다. 내가 사는 곳에서 무작위로 정전이 발생하기 때문에 이 작업을 수행했습니다. */ get_feed_time1(); get_feed_time2(); get_completed_feedings(); get_portion();}//======================================================================================================무효 루프() { 변경_상태(); 체크_버튼(); check_feedtime(); check_rtc(); manual_feed_check ();}//================기능 =======================/* 사용 JC_Button 라이브러리는 버튼이 눌렸는지 지속적으로 확인합니다. 어떤 버튼을 누르느냐에 따라 menu_transitions*/void check_buttons() { enterBtn.read(); upBtn.read(); downBtn.read(); backBtn.read(); manualFeedBtn.read(); if (enterBtn.wasPressed()) { Serial.println("Enter를 눌렀습니다!"); 사용자 입력 =btnENTER; menu_transitions(userInput); } if (upBtn.wasPressed()) { Serial.println("당신이 눌렀습니다!"); 사용자 입력 =btnUP; menu_transitions(userInput); } if (downBtn.wasPressed()) { Serial.println("당신이 눌렀습니다!"); 사용자 입력 =btnDOWN; menu_transitions(userInput); } if (backBtn.wasPressed()) { Serial.println("뒤로 눌렀습니다!"); 사용자 입력 =btnBACK; menu_transitions(userInput); } if (manualFeedBtn.wasPressed()) { Serial.println("수동으로 먹이고 있습니다!"); manualFeed =참; }}//======================================================/* 이 함수는 현재 있는 메뉴 또는 "상태"에 따라 표시되는 내용을 결정합니다. 각 메뉴에는 해당 메뉴를 표시하는 함수가 있습니다.*/void changing_states() { switch (state) { 케이스 MAIN:display_current_time(); display_feeding_times(); display_portion(); 부서지다; 케이스 MENU_EDIT_FEED1:display_set_feed_time1_menu(); 부서지다; 케이스 MENU_EDIT_FEED2:display_set_feed_time2_menu(); 부서지다; 케이스 MENU_EDIT_TIME:display_set_time_menu(); 부서지다; 케이스 MENU_EDIT_PORTION:display_set_portion_menu(); 부서지다; 경우 EDIT_FEED1_HOUR:set_feed_time1(); 부서지다; 경우 EDIT_FEED1_MIN:set_feed_time1(); 부서지다; 사례 EDIT_FEED2_HOUR:set_feed_time2(); 부서지다; 경우 EDIT_FEED2_MIN:set_feed_time2(); 부서지다; 경우 EDIT_HOUR:set_the_time(); 부서지다; 경우 EDIT_MIN:set_the_time(); 부서지다; 사례 EDIT_PORTION:set_the_portion(); 부서지다; }}//======================================================/* 상태 머신의 과도기적 부분입니다. 이것은 한 메뉴에서 다른 메뉴로 이동하여 값을 변경할 수 있도록 합니다.*/void menu_transitions(int input) { switch (state) { case MAIN:if (input ==btnENTER) { lcd.clear(); 상태 =MENU_EDIT_FEED1; } if (입력 ==btnBACK) { Hall_sensor_fail =false; } 부서지다; //------------------------------------------------ ---- case MENU_EDIT_FEED1:if (input ==btnBACK) { lcd.clear(); 상태 =메인; } else if (입력 ==btnENTER) { lcd.clear(); 상태 =EDIT_FEED1_HOUR; } else if (입력 ==btnDOWN) { lcd.clear(); 상태 =MENU_EDIT_FEED2; } 부서지다; //------------------------------------------------ ---- case EDIT_FEED1_HOUR:// 시간을 설정하는 동안 서보가 꺼지는 것을 방지하기 위해 필요합니다. servoOn =false; if (입력 ==btnUP) { feed_time1_hour++; if (feed_time1_hour> 23) { feed_time1_hour =0; } } else if (입력 ==btnDOWN) { feed_time1_hour--; if (feed_time1_hour <0) { feed_time1_hour =23; } } else if (입력 ==btnBACK) { lcd.clear(); 서보온 =참; 상태 =MENU_EDIT_FEED1; } else if (입력 ==btnENTER) { 상태 =EDIT_FEED1_MIN; } 부서지다; //---------------------------------------------------- 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*"); 지연(1000); lcd.clear(); servoOn =true; write_feeding_time1 (); state =MAIN; } 부서지다; //---------------------------------------------------- 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; } 부서지다; //---------------------------------------------------- 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; } 부서지다; //---------------------------------------------------- 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*"); 지연(1000); lcd.clear(); servoOn =true; write_feeding_time2 (); state =MAIN; } 부서지다; //---------------------------------------------------- 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; } 부서지다; //---------------------------------------------------- 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; } 부서지다; //---------------------------------------------------- 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*"); 지연(1000); lcd.clear(); rtc.adjust(DateTime(0, 0, 0, Hour, Minute, 0)); state =MAIN; } 부서지다; //---------------------------------------------------- case MENU_EDIT_PORTION:if (input ==btnUP) { lcd.clear(); state =MENU_EDIT_TIME; } else if (input ==btnENTER) { lcd.clear(); state =EDIT_PORTION; } 부서지다; //---------------------------------------------------- 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*"); 지연(1000); lcd.clear(); write_portion(); state =MAIN; } 부서지다; }}//=====================================================// 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); 부서지다; //---------------------------------------------------- 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(" "); } 부서지다; } 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); 부서지다; //---------------------------------------------------- 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(" "); } 부서지다; } 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); 부서지다; //---------------------------------------------------- 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(" "); } 부서지다; } 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 =낮음; } 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.ino

    맞춤형 부품 및 인클로저

    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.git

    회로도

    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.

    제조공정

    1. 3D 인쇄된 부품을 크레용으로 마무리...?
    2. RPi가 있는 라인 추적 센서
    3. RPi가 있는 환경 센서 API
    4. Portenta 및 열전쌍 센서(MAX6675 포함)
    5. 3D 인쇄 부품으로 더 나은 품질 관리
    6. 기능적인 3D 프린팅 부품으로 몸을 가누지 마십시오!
    7. 3D 인쇄 부품으로 고 카트를 재창조
    8. 웹 세미나:Eiger로 강력한 3D 인쇄 부품 인쇄
    9. 3D 인쇄 익스트림 드론
    10. 3D 프린팅 공식 SAE 차량 부품으로 결승점을 향한 경주