Introduction: Cal Poly CPE 133 Automatic Pet Feeder (VHDL)
Automation has made today's life more convenient. Especially for lazy people. And that's exactly what engineers are. Lazy people who build gizmos and gadgets to do their job so they can continue being lazy. It's all about efficiency. And that's why I'm proud to present an Automatic Pet Feeder made using Xilinx VIvado (VHDL) and a Basys 3 Artix-7 FPGA trainer board.
Reasons for this chosen project are for the sake of potentially making someone's life a bit easier, but most importantly, so I can get a good grade in this class (hi Professor Hummel). I don't even have a pet.
Step 1: Step 1: Materials
For this project, all you need is:
1. Xilinx Vivado (I used version 2016.3)
2. A Basys3 Artix-7 FPGA trainer board
3. A 3.3 V Servo Motor (Due to being a dirt poor college student, I chose paying rent over buying another servo. Instead, I used a 4.8V SG90 Servo Motor I had lying around).
4. Wires to connect the servo to the pmod connectors of the board
Step 2: Step 2: Planning Out the Feeder
The feeder operates in 3 different states which I like to call the TellTime, setTime, and setAlarm. Our time and our alarm will all be in the form of a 16 bit number, which we will divide into four 4 bit numbers that will correspond to the 4 digits of the Basys 3 7-Segment Display.
TellTime: This state will be our default. It's going to be our clock, counting 60 seconds to update the clock a minute. All states will be displayed on the Basys3 7-Segment Display.
setTime: This state will be where we are able to set the time for our clock. We'll switch between all the states using switches on the Basys 3 board. In order to add or subtract from the time, we'll use the up and down buttons on the board as well.
setAlarm: Similar to setTime, this state will also implement the up and down buttons to add or subtract time. However, it will subtract and add from a separate alarm time we will have saved in memory.
The alarm will trigger using another state machine that will be comparing the current time from TellTime with the alarm in memory from setAlarm. If the two times are equal to each other, it will send a signal to an LED as well as powering the servo using a PWM. Within the alarm itself will be two states, one that will be the alarm being on, and another that we will call Skip. Skip will be also accessed by another switch on the board and while active, will deactivate the alarm.
Step 3: Step 3: VHDL Code (Structure and TellTime)
The first design source is our main module, named Feeder.
We will first have our inputs and outputs.
Inputs: For our inputs we will need a clock input (Clk) for our system clock, a 2 bit vector to switch between our states (State), our two buttons (Up, Down), and one more input for our Skip switch.
Outputs: For our 7-segment display we need two outputs, a 7 bit (SevSeg) for the cathodes of our display, and a 4-bit (Anodes) for the common anodes. We'll also need 3 more outputs, for our Servo (Servo), and our LEDs (Led0, Led15).
TellTime is the first part of our first state machine that will also include setTime and setAlarm. In this process block, we will have Clk, state, up, and down in our sensitivity list.
1. The system clock on our board operates at 100MHz, which means it will pulse 100,000,000 times every second. We want our clock to count seconds, so we will create a clock divider that will add 1 to a signal seconds every 100,000,000 pulses, or 1 second using another signal count.
2. We will distinguish TellTime as our default state for when state is "00" or "11", using a case statement, making it our state when both state switches are off or are both on.
3. In this case, we will use the signal seconds. When seconds reaches 59, we will add 1 to the our 16 bit number that represents the time (d1,d2,d3,d4 which we have defaulted to "0000100000000000" or 0800). We add 1 to d4 first. Since our clock runs on a 12 hour cycle we must check to see if the 16 bit number has reached the equivalent of 1259. If so we will return it to read 0100. Otherwise, we check to see if d4 has reached 9, reset it to 0 and add 1 to d3. If d3 has reached 5, we reset it to 0 and add 1 to d2. If d2 has reached 9 we reset it to 0 and add 1 to d1. If d1 has reached 1 we reset it to 0 and add 1 to d4.
4. After all this, we reset seconds to "000000" in order to count for the next minute.
Step 4: Step 4: SetTime
SetTime is the case for when state is equal to "01". It operates similarly to TellTime but instead of using the clock divider to add time we will use the two up and down buttons to add and subtract minutes from the time. The subtraction is simply the reverse of the addition portion. Remember that the system clock pulses 100,000,000 times a second, so even the fastest press of the button from a human finger will add/subtract the minutes from the clock too quickly. To solve this issue we create a signal button which we have defaulted to 1. When we press either button, we add or subtract from the 16 bit number similarly to TellTime but also toggle button to 0. This way we only get 1 pulse at a time from the clock, so we can visually follow how our time is changing as we press the buttons.
Step 5: Step 5: SetAlarm
SetAlarm is the case for when state is equal to "10". This state uses the exact logic for setTime except for saving the 16 bit number in different alarm variables (a1, a2, a3, a4). This concludes the first state machine for our automatic pet feeder.
Step 6: Step 6: the Alarm
The alarm is its own state machine that takes in the system Clk in its sensitivity list.
1. First we will check if Skip has been toggled on the board. If so, we light up our Led15 above the switch for Skip and make sure the alarm Led0 is off.
2. If Skip is off, we now check if a1, a2, a3, a4 and d1, d2, d3, d4 are equal to each other respectively. If they are, we move onto our PWM for our servo.
3. My SG90 servo is rated for 4.8 V while the basys 3 board has only 3.3 V from it's pmod connector, so mine will not be running optimally. For the PWM, or pulse width modulator, our servo uses a 20ms (50Hz) PWM period with a 1-2 ms duty cycle. When the duty cycle is 2 ms the servo will turn all the way to the right from its center position, and when it is 1 ms, the servo will turn all the way to the left. We will conceptualize the right to be our "open" position of a pet feeder door, and left to be closed.
4. As for the code of the PWM, it acts as a two clock dividers that will toggle a signal pwm to create the square wave. We will use another counter for the system clock. When the alarm and time are equal, and if pwm is HIGH, we will count to 200,000, or 2 ms (5 Hz) before toggling our output signal Clk_a HIGH and our pwm LOW. And if pwm is LOW we will then count to 1,800,000 or 18ms to make complete the 20 ms PWM period. Then we will toggle Clk_a to LOW, pwm to HIGH, and reset our counter back to 0. We will then wire our servo output to our clk_a and turn on Led0 as another indicator for our alarm going off.
5. To close the "door" of our automatic pet feeder once the alarm has finished, we simply repeat the PWM we made except using a 1 ms duty cycle and turning off the alarm LED.
6. Our servo has 3 wires, power, ground, and signal. We attach power and ground to pin 5 and pin 6 of the pmod respectively and signal to any of the other pins, remembering to use the correct pin number in the constraints file.
Step 7: Step 7: Seven Segment Display
To display our time, we will create a sub-module for the 7-Segment Display on the board. In this sub-module we will take in the system clock (Clk), the 16 bit alarm, the 16 bit time, and the state as inputs. For outputs, we will have the 7 bit individual cathodes (SevSeg) and the 4-bit common anodes (Anodes).
1. First we will have to create another clock divider. Since the 7 segment display has common anodes, when you would normally display something on the display, it will appear for all 4 digits. This resulting clock (Clk_out) will cycle through the four anodes displaying a different number each pulse at such a speed that to the human eye it appears as four different numbers being constantly shown.
2. We will use a 2 bit variable digit to cycle through the four anodes, adding 1 to digit every clock pulse. Depending on which state we are in, we will either display the alarm (a1, a2, a3, a4) or the time (d1, d2, d3, d4). The segments of the display operate inversely, so them being LOW will light them up. So using the reference manual we can find the right combination to display the numbers we want.
3. Lastly we will return to the Feeder module and create a component of the SevSegDisplay and connect the corresponding ports.
4. We will finally create the constraints file that will connect our inputs and outputs of our feeder module to the Basys 3 board.