Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 04:35 23 Mar 2026 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Automatic fish feeder

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 11099
Posted: 11:15am 22 Mar 2026
Copy link to clipboard 
Print this post

Pico2 + Waveshare RTC + Pico-LCD-1.3 + Stepper driver module + TMC2209 driver
Everything 3D printed. STLs available if anyone wants them. Uses archimedian screw to deliver the food.
Runs with a CPU speed of 48MHz which results in a current draw of about 25mA at 12V so an old car battery should run it for a month easily.
option list
PicoMite MMBasic RP2350A V6.02.01RC8
OPTION SYSTEM SPI GP10,GP11,GP28
OPTION SYSTEM I2C GP6,GP7
OPTION AUTORUN  ON
OPTION FLASH SIZE 4194304
OPTION COLOURCODE ON
OPTION CPUSPEED (KHz) 48000
OPTION DISPLAY 64, 132
OPTION LCDPANEL ST7789, LANDSCAPE,GP8,GP12,GP9,GP13
















' FishFeeder.bas
' Automated fish feeder UI for PicoMite (240x240 display + buttons/joystick)
' Navigation:
' A = next page, B = previous page
' CTRL = toggle edit mode on current page
' UP/DOWN = change selected field/value in edit mode
' LEFT/RIGHT = move field selection in edit mode
' X = manual feed now (from HOME page)
' Y = force RTC SETTIME (from CLOCK page)

Option Explicit

On Error Skip
Stepper Close
Font 1,2
Const a = "GP15"
Const b = "GP17"
Const xbtn = "GP19"
Const ybtn = "GP21"
Const up = "GP2"
Const down = "GP18"
Const leftpin = "GP16"
Const rightpin = "GP20"
Const ctrl = "GP3"

Const dir = "GP27"
Const steppin = "GP26"
Const enable = "GP22"

Const FEED_MAX = 6
Const FEED_DEFAULT_ON = 2
Const UNIT_MM = 2
Const STEP_FEEDRATE = 200
Const PRIME_STEP_MM = 5

Const PAGE_HOME = 0
Const PAGE_CLOCK = 1
Const PAGE_FEED0 = 2
Const PAGE_FEED_LAST = PAGE_FEED0 + FEED_MAX - 1
Const PAGE_HELP = PAGE_FEED_LAST + 1
Const PAGE_PRIME = PAGE_HELP + 1
Const PAGE_LAST = PAGE_PRIME

Const EV_NONE = 0
Const EV_A = 1
Const EV_B = 2
Const EV_X = 3
Const EV_Y = 4
Const EV_UP = 5
Const EV_DOWN = 6
Const EV_LEFT = 7
Const EV_RIGHT = 8
Const EV_CTRL = 9

Dim feedHour%(FEED_MAX)
Dim feedMin%(FEED_MAX)
Dim feedUnits%(FEED_MAX)
Dim feedEnabled%(FEED_MAX)

Dim page% = PAGE_HOME
Dim editMode% = 0
Dim fieldSel% = 0
Dim needRedraw% = 1
Dim lastFeedKey$ = ""
Dim startupMinuteKey$ = ""
Dim scheduleDirty% = 0
Dim ev% = EV_NONE
Dim notfirst% = 0

Dim setYear% = 2026
Dim setMonth% = 1
Dim setDay% = 1
Dim setHour% = 12
Dim setMin% = 0
Dim setSec% = 0

Dim prevA% = 0, prevB% = 0, prevX% = 0, prevY% = 0
Dim prevUp% = 0, prevDown% = 0, prevLeft% = 0, prevRight% = 0, prevCtrl% = 0
Dim primeEndpos! = 0
Dim backlightTimer% = 1500

InitPins
SeedButtonHistory
InitStepper
LoadSchedule
InitDefaults
LoadClockIntoSetFields
SetStartupMinuteKey
Backlight 100

Do
 ev% = ReadEvent%()

 If ev% <> EV_NONE Then
   HandleEvent ev%
   needRedraw% = 1
   backlightTimer% = 1500
   Backlight 100
 End If

 HandleXActions

 If XPressed%() Then
   backlightTimer% = 1500
   Backlight 100
 End If

 CheckAutoFeed

 If scheduleDirty% Then
   SaveSchedule
   scheduleDirty% = 0
 End If

 If needRedraw% Then
   DrawPage
   needRedraw% = 0
 End If

 If backlightTimer% > 0 Then
   Inc backlightTimer%, -1
   If backlightTimer% = 0 Then Backlight 0
 End If

 Pause 40
Loop

Sub InitPins
 TrySetInput a
 TrySetInput b
 TrySetInput xbtn
 TrySetInput ybtn
 TrySetInput up
 TrySetInput down
 TrySetInput leftpin
 TrySetInput rightpin
 TrySetInput ctrl
 Pause 300
End Sub

Sub TrySetInput(pin$)
 On Error Skip 1
 SetPin pin$, DIN, PULLUP
End Sub

Sub SeedButtonHistory
 prevA% = SafeBtn%(a)
 prevB% = SafeBtn%(b)
 prevX% = XPressed%()
 prevY% = SafeBtn%(ybtn)
 prevUp% = SafeBtn%(up)
 prevDown% = SafeBtn%(down)
 prevLeft% = SafeBtn%(leftpin)
 prevRight% = SafeBtn%(rightpin)
 prevCtrl% = SafeBtn%(ctrl)
End Sub

Sub InitStepper
 Stepper Init
 Stepper Axis X, GP26, GP27, GP22, 1, 800, 100, 100
 Stepper Position X, 0.1
 Stepper Run 1
 Stepper gc g1 x0 f100
End Sub

Sub InitDefaults
 Local i%
 If notfirst% Then Exit Sub
 For i% = 1 To FEED_MAX
   feedHour%(i%) = 8 + i%
   If feedHour%(i%) > 23 Then feedHour%(i%) = feedHour%(i%) - 24
   feedMin%(i%) = 0
   feedUnits%(i%) = 1
   If i% <= FEED_DEFAULT_ON Then
     feedEnabled%(i%) = 1
   Else
     feedEnabled%(i%) = 0
   End If
 Next i%
End Sub

Sub LoadSchedule
'  On Error Skip 1
 VAR RESTORE
 NormalizeSchedule
End Sub

Sub SaveSchedule
 On Error Skip 1
 notfirst%=1
 VAR SAVE feedHour%(), feedMin%(), feedUnits%(), feedEnabled%(), notfirst%
End Sub

Sub NormalizeSchedule
 Local i%
 For i% = 1 To FEED_MAX
   If feedHour%(i%) < 0 Then feedHour%(i%) = 0
   If feedHour%(i%) > 23 Then feedHour%(i%) = 23
   If feedMin%(i%) < 0 Then feedMin%(i%) = 0
   If feedMin%(i%) > 59 Then feedMin%(i%) = 59
   If feedUnits%(i%) < 1 Then feedUnits%(i%) = 1
   If feedUnits%(i%) > 20 Then feedUnits%(i%) = 20
   If feedEnabled%(i%) <> 0 Then
     feedEnabled%(i%) = 1
   Else
     feedEnabled%(i%) = 0
   End If
 Next i%
End Sub

Sub HandleEvent(ev%)
 If ev% = EV_A Then
   If page% < PAGE_LAST Then
     page% = page% + 1
   Else
     page% = PAGE_HOME
   End If
   editMode% = 0
   fieldSel% = 0
   Exit Sub
 End If

 If ev% = EV_B Then
   If page% > PAGE_HOME Then
     page% = page% - 1
   Else
     page% = PAGE_LAST
   End If
   editMode% = 0
   fieldSel% = 0
   Exit Sub
 End If

 If ev% = EV_X Then
   If page% = PAGE_HOME Or (page% >= PAGE_FEED0 And page% <= PAGE_FEED_LAST) Then
     ManualFeed
   End If
   Exit Sub
 End If

 If page% = PAGE_CLOCK And ev% = EV_Y Then
   ApplyRtc
   Exit Sub
 End If

 If ev% = EV_CTRL Then
   editMode% = 1 - editMode%
   Exit Sub
 End If

 If editMode% = 0 Then Exit Sub

 Select Case page%
   Case PAGE_CLOCK
     EditClock ev%
   Case PAGE_FEED0 To PAGE_FEED_LAST
     EditFeed page% - PAGE_FEED0 + 1, ev%
 End Select
End Sub

Sub HandleXActions
 If page% <> PAGE_PRIME Then Exit Sub

 If XPressed%() Then
   If primeEndpos! = 0 Then
     Stepper Position X, 0
   End If
   If Peek(STEPPER BUFFER) > 10 Then
     Inc primeEndpos!, 0.1
     Stepper GCODE G1, X, primeEndpos!, F, STEP_FEEDRATE
   End If
 Else
   primeEndpos! = 0
 End If
End Sub

Sub EditClock(ev%)
 If ev% = EV_LEFT And fieldSel% > 0 Then fieldSel% = fieldSel% - 1
 If ev% = EV_RIGHT And fieldSel% < 5 Then fieldSel% = fieldSel% + 1

 If ev% = EV_UP Then
   Select Case fieldSel%
     Case 0: Inc setDay%: If setDay% > 31 Then setDay% = 1
     Case 1: Inc setMonth%: If setMonth% > 12 Then setMonth% = 1
     Case 2: Inc setYear%: If setYear% > 2099 Then setYear% = 2000
     Case 3: Inc setHour%: If setHour% > 23 Then setHour% = 0
     Case 4: Inc setMin%: If setMin% > 59 Then setMin% = 0
     Case 5: Inc setSec%: If setSec% > 59 Then setSec% = 0
   End Select
 End If

 If ev% = EV_DOWN Then
   Select Case fieldSel%
     Case 0: Inc setDay%,-1: If setDay% < 1 Then setDay% = 31
     Case 1: Inc setMonth%,-1: If setMonth% < 1 Then setMonth% = 12
     Case 2: Inc setYear%,-1: If setYear% < 2000 Then setYear% = 2099
     Case 3: Inc setHour%,-1: If setHour% < 0 Then setHour% = 23
     Case 4: Inc setMin%,-1: If setMin% < 0 Then setMin% = 59
     Case 5: Inc setSec%,-1: If setSec% < 0 Then setSec% = 59
   End Select
 End If

End Sub

Sub EditFeed(idx%, ev%)
 If ev% = EV_LEFT And fieldSel% > 0 Then fieldSel% = fieldSel% - 1
 If ev% = EV_RIGHT And fieldSel% < 3 Then fieldSel% = fieldSel% + 1

 If ev% = EV_UP Then
   Select Case fieldSel%
     Case 0: feedEnabled%(idx%) = 1: scheduleDirty% = 1
     Case 1
       Inc feedHour%(idx%)
       If feedHour%(idx%) > 23 Then feedHour%(idx%) = 0
       scheduleDirty% = 1
     Case 2
       Inc feedMin%(idx%)
       If feedMin%(idx%) > 59 Then feedMin%(idx%) = 0
       scheduleDirty% = 1
     Case 3
       Inc feedUnits%(idx%)
       If feedUnits%(idx%) > 20 Then feedUnits%(idx%) = 20
       scheduleDirty% = 1
   End Select
 End If

 If ev% = EV_DOWN Then
   Select Case fieldSel%
     Case 0: feedEnabled%(idx%) = 0: scheduleDirty% = 1
     Case 1
       Inc feedHour%(idx%),-1
       If feedHour%(idx%) < 0 Then feedHour%(idx%) = 23
       scheduleDirty% = 1
     Case 2
       Inc feedMin%(idx%),-1
       If feedMin%(idx%) < 0 Then feedMin%(idx%) = 59
       scheduleDirty% = 1
     Case 3
       Inc feedUnits%(idx%),-1
       If feedUnits%(idx%) < 1 Then feedUnits%(idx%) = 1
       scheduleDirty% = 1
   End Select
 End If

End Sub

Sub ApplyRtc
 Local ys%, ms%, ds%, hs%, ns%, ss%
 ys% = setYear%
 ms% = setMonth%
 ds% = setDay%
 hs% = setHour%
 ns% = setMin%
 ss% = setSec%

 RTC SETTIME ys%, ms%, ds%, hs%, ns%, ss%
 Pause 50
 LoadClockIntoSetFields
End Sub

Sub LoadClockIntoSetFields
 Local d$, t$
 d$ = Date$
 t$ = Time$

 setDay% = Val(Mid$(d$, 1, 2))
 setMonth% = Val(Mid$(d$, 4, 2))
 setYear% = 2000 + Val(Right$(d$, 2))

 setHour% = Val(Mid$(t$, 1, 2))
 setMin% = Val(Mid$(t$, 4, 2))
 setSec% = Val(Mid$(t$, 7, 2))
End Sub

Sub CheckAutoFeed
 Local t$, d$, hh%, mm%, i%
 Local key$, minuteKey$

 t$ = Time$
 d$ = Date$
 hh% = Val(Mid$(t$, 1, 2))
 mm% = Val(Mid$(t$, 4, 2))
 minuteKey$ = Right$(d$, 2) + Mid$(d$, 4, 2) + Left$(d$, 2) + Right$("0" + Str$(hh%), 2) + Right$("0" + Str$(mm%), 2)

 If minuteKey$ = startupMinuteKey$ Then Exit Sub
 startupMinuteKey$ = minuteKey$

 For i% = 1 To FEED_MAX
   If feedEnabled%(i%) And hh% = feedHour%(i%) And mm% = feedMin%(i%) Then
     key$ = Right$(d$, 2) + Mid$(d$, 4, 2) + Left$(d$, 2) + Right$("0" + Str$(hh%), 2) + Right$("0" + Str$(mm%), 2) + Str$(i%)
     If key$ <> lastFeedKey$ Then
       DispenseUnits feedUnits%(i%)
       lastFeedKey$ = key$
     End If
   End If
 Next i%
End Sub

Sub SetStartupMinuteKey
 Local t$, d$, hh%, mm%
 t$ = Time$
 d$ = Date$
 hh% = Val(Mid$(t$, 1, 2))
 mm% = Val(Mid$(t$, 4, 2))
 startupMinuteKey$ = Right$(d$, 2) + Mid$(d$, 4, 2) + Left$(d$, 2) + Right$("0" + Str$(hh%), 2) + Right$("0" + Str$(mm%), 2)
End Sub

Sub ManualFeed
 Local idx%, i%
 idx% = 1
 If page% >= PAGE_FEED0 And page% <= PAGE_FEED_LAST Then
   idx% = page% - PAGE_FEED0 + 1
   DispenseUnits feedUnits%(idx%)
   Exit Sub
 End If

 For i% = 1 To FEED_MAX
   If feedEnabled%(i%) Then
     idx% = i%
     Exit For
   End If
 Next i%

 If idx% <= FEED_MAX And feedEnabled%(idx%) = 0 Then Exit Sub
 DispenseUnits feedUnits%(idx%)
End Sub

Sub DispenseUnits(units%)
 Local dist!
 If units% < 1 Then units% = 1
 dist! = units% * UNIT_MM

 Stepper Position X, 0
 Stepper GCODE G1, X, dist!, F, STEP_FEEDRATE

 Do While Peek(STEPPER ACTIVE)
 Loop
End Sub

Sub DrawPage
 CLS
 Select Case page%
   Case PAGE_HOME
     DrawHome
   Case PAGE_CLOCK
     DrawClock
   Case PAGE_FEED0 To PAGE_FEED_LAST
     DrawFeed page% - PAGE_FEED0 + 1
   Case PAGE_HELP
     DrawHelp
   Case PAGE_PRIME
     DrawPrime
 End Select
End Sub

Sub DrawHelp
 Text 4, 4, "HELP"
 Text 4, 28, "A NEXT PAGE"
 Text 4, 52, "B PREV PAGE"
 Text 4, 76, "CTRL EDIT"
 Text 4, 100, "UPDN CHANGE"
 Text 4, 124, "LR MOVE"
 Text 4, 148, "X FEED NOW"
 Text 4, 172, "Y RTC SET"
End Sub

Sub DrawPrime
 Text 4, 4, "PRIME"
 Text 4, 28, "HOLD X RUN"
 Text 4, 52, "REL X STOP"
 Text 4, 76, "CONT MOVE"
 Text 4, 100, "A/B PAGE"
End Sub

Sub DrawHome
 Local nexti%, i%, nowMins%, fm%, m%, t$
 Local dMins%, cH%, cM%
 t$ = Time$
 nowMins% = Val(Mid$(t$, 1, 2)) * 60 + Val(Mid$(t$, 4, 2))

 nexti% = 1
 fm% = 24 * 60 + 1

 For i% = 1 To FEED_MAX
   If feedEnabled%(i%) Then
     m% = feedHour%(i%) * 60 + feedMin%(i%)
     If m% >= nowMins% And m% < fm% Then
       fm% = m%
       nexti% = i%
     End If
   End If
 Next i%

 If fm% = 24 * 60 + 1 Then
   For i% = 1 To FEED_MAX
     If feedEnabled%(i%) Then
       fm% = feedHour%(i%) * 60 + feedMin%(i%)
       nexti% = i%
       Exit For
     End If
   Next i%
 End If

 Text 4, 4, "FISH FEEDER"
 Text 4, 28, Left$(Date$, 8)
 Text 4, 52, Left$(Time$, 8)
 Text 4, 76, "NEXT F" + Trim$(Str$(nexti%))
 If fm% = 24 * 60 + 1 Then
   Text 4, 100, "--:--"
   Text 4, 172, "IN --:--"
 Else
   Text 4, 100, TwoDigit$(feedHour%(nexti%)) + ":" + TwoDigit$(feedMin%(nexti%))
   dMins% = fm% - nowMins%
   If dMins% < 0 Then dMins% = dMins% + (24 * 60)
   cH% = dMins% \ 60
   cM% = dMins% Mod 60
   Text 4, 172, "IN " + TwoDigit$(cH%) + ":" + TwoDigit$(cM%)
 End If
 Text 4, 124, "EN " + Trim$(Str$(EnabledCount%()))
 Text 4, 148, "X FEED NOW"
End Sub

Sub DrawClock
 Local m$, markX%
 markX% = 240 - (3 * MM.FONTWIDTH)
 If editMode% Then
   m$ = "EDIT"
 Else
   m$ = "VIEW"
 End If

 Text 4, 4, "CLOCK " + m$
 Text 4, 28, "D " + TwoDigit$(setDay%) + "/" + TwoDigit$(setMonth%)
 Text 4, 52, "Y " + Trim$(Str$(setYear%))
 Text 4, 76, "T " + TwoDigit$(setHour%) + ":" + TwoDigit$(setMin%)
 Text 4, 100, "S " + TwoDigit$(setSec%)
 Text 4, 124, "Y RTC SET"
 Text 4, 148, "C EDIT"

 If editMode% Then
   Select Case fieldSel%
     Case 0: Text markX%, 28, "<D"
     Case 1: Text markX%, 28, "<M"
     Case 2: Text markX%, 52, "<Y"
     Case 3: Text markX%, 76, "<H"
     Case 4: Text markX%, 76, "<N"
     Case 5: Text markX%, 100, "<S"
   End Select
 End If
End Sub

Sub DrawFeed(idx%)
 Local m$, en$, markX%
 markX% = 240 - (3 * MM.FONTWIDTH)
 If feedEnabled%(idx%) Then
   en$ = "ON"
 Else
   en$ = "OFF"
 End If

 If editMode% Then
   m$ = "EDIT"
 Else
   m$ = "VIEW"
 End If

 Text 4, 4, "FEED " + Trim$(Str$(idx%))
 Text 4, 28, "STATE " + en$
 Text 4, 52, "TIME " + TwoDigit$(feedHour%(idx%)) + ":" + TwoDigit$(feedMin%(idx%))
 Text 4, 76, "UNITS " + Trim$(Str$(feedUnits%(idx%)))
 Text 4, 100, "X TEST"
 Text 4, 124, m$

 If editMode% Then
   Select Case fieldSel%
     Case 0: Text markX%, 28, "<E"
     Case 1: Text markX%, 52, "<H"
     Case 2: Text markX%, 52, "<M"
     Case 3: Text markX%, 76, "<U"
   End Select
 End If
End Sub

Function TwoDigit$(v%)
 TwoDigit$ = Right$("0" + Trim$(Str$(v%)), 2)
End Function

Function EnabledCount%()
 Local i%, c%
 c% = 0
 For i% = 1 To FEED_MAX
   If feedEnabled%(i%) Then Inc c%
 Next i%
 EnabledCount% = c%
End Function

Function ReadEvent%()
 Local aNow%, bNow%, xNow%, yNow%
 Local uNow%, dNow%, lNow%, rNow%, cNow%

 aNow% = SafeBtn%(a)
 bNow% = SafeBtn%(b)
 xNow% = XPressed%()
 yNow% = SafeBtn%(ybtn)
 uNow% = SafeBtn%(up)
 dNow% = SafeBtn%(down)
 lNow% = SafeBtn%(leftpin)
 rNow% = SafeBtn%(rightpin)
 cNow% = SafeBtn%(ctrl)

 ReadEvent% = EV_NONE

 If aNow% And Not prevA% Then
   ReadEvent% = EV_A
 ElseIf bNow% And Not prevB% Then
   ReadEvent% = EV_B
 ElseIf xNow% And Not prevX% Then
   ReadEvent% = EV_X
 ElseIf yNow% And Not prevY% Then
   ReadEvent% = EV_Y
 ElseIf uNow% And Not prevUp% Then
   ReadEvent% = EV_UP
 ElseIf dNow% And Not prevDown% Then
   ReadEvent% = EV_DOWN
 ElseIf lNow% And Not prevLeft% Then
   ReadEvent% = EV_LEFT
 ElseIf rNow% And Not prevRight% Then
   ReadEvent% = EV_RIGHT
 ElseIf cNow% And Not prevCtrl% Then
   ReadEvent% = EV_CTRL
 End If

 prevA% = aNow%
 prevB% = bNow%
 prevX% = xNow%
 prevY% = yNow%
 prevUp% = uNow%
 prevDown% = dNow%
 prevLeft% = lNow%
 prevRight% = rNow%
 prevCtrl% = cNow%
End Function

Function SafeBtn%(pin$)
 Local v%
 v% = 0
 v% = (Pin(pin$) = 0)
 SafeBtn% = v%
End Function

Function SafePin%(pin$)
 Local v%
 v% = 1
 On Error Skip 1
 v% = Pin(pin$)
 SafePin% = v%
End Function

Function XPressed%()
 XPressed% = SafeBtn%(xbtn)
End Function

Edited 2026-03-22 21:33 by matherp
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2026