2016-10-08 21:39:50 -07:00
|
|
|
#define __DEBUG__ 1
|
|
|
|
|
|
|
|
#include <avr/io.h>
|
|
|
|
#include <util/delay.h>
|
|
|
|
#include <libs/serialdebug.h>
|
|
|
|
#include <libs/macros.h>
|
|
|
|
#include <avr/interrupt.h>
|
|
|
|
#include <avr/sleep.h>
|
|
|
|
#include <avr/wdt.h>
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
/* User settings */
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// How long between feedings is calculated as follows:
|
|
|
|
// BLOCK_BASE + ($setting * BLOCK_STEP)
|
|
|
|
// All units are minutes.
|
|
|
|
// $setting is the 0-15 value read from the speed knob
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
#define BLOCK_BASE 360
|
|
|
|
#define BLOCK_STEP 27 // I wanted 30 mins but my chip's clock is off
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// How long to pour for, in milliseconds
|
|
|
|
// Cannot be larger than an unsigned int
|
|
|
|
#define DISPENSE_MS 12000
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// When the device boots, it wiggles the motor to notify the user that is is alive.
|
|
|
|
// These settings control the length and spacing of the pulses, in milliseconds
|
|
|
|
#define WAKEUP_PULSE 100
|
|
|
|
#define WAKEUP_BETWEEN 75
|
|
|
|
#define NUM_PULSES 6
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
/* Ports configuration */
|
|
|
|
|
|
|
|
// Attiny84 pin layout:
|
|
|
|
// vcc [1 14] gnd
|
|
|
|
// PB0 [2 13] PA0 input speed_0
|
|
|
|
// PB1 [3 12] PA1 input speed_1
|
|
|
|
// rst PB3 [4 11] PA2 input speed_2
|
|
|
|
// INT/input feed button PB2 [5 10] PA3 input speed_3
|
|
|
|
// motor control PA7 [6 9] PA4
|
|
|
|
// debug serial PA6 [7 8] PA5 dial/motor enable
|
|
|
|
|
|
|
|
// Motor control
|
2016-10-08 21:39:50 -07:00
|
|
|
#define FEED_DDR DDRA
|
|
|
|
#define FEED_PORT PORTA
|
|
|
|
#define FEED_BIT PA7
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// Dial/motor enable
|
2016-10-08 21:39:50 -07:00
|
|
|
#define PWRON_DDR DDRA
|
|
|
|
#define PWRON_PORT PORTA
|
|
|
|
#define PWRON_BIT PA5
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// Force-feed input button - must be an interrupt pin
|
2016-10-08 21:39:50 -07:00
|
|
|
#define FORCE_DDR DDRB
|
|
|
|
#define FORCE_PORT PINB
|
|
|
|
#define FORCE_BIT PB2
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// Dial inputs - NIBBLE0 is MSB
|
2016-10-08 21:39:50 -07:00
|
|
|
#define NIBBLE0_DDR DDRA
|
|
|
|
#define NIBBLE0_PORT PINA
|
|
|
|
#define NIBBLE0_BIT PA0
|
|
|
|
|
|
|
|
#define NIBBLE1_DDR DDRA
|
|
|
|
#define NIBBLE1_PORT PINA
|
|
|
|
#define NIBBLE1_BIT PA1
|
|
|
|
|
|
|
|
#define NIBBLE2_DDR DDRA
|
|
|
|
#define NIBBLE2_PORT PINA
|
|
|
|
#define NIBBLE2_BIT PA2
|
|
|
|
|
|
|
|
#define NIBBLE3_DDR DDRA
|
|
|
|
#define NIBBLE3_PORT PINA
|
|
|
|
#define NIBBLE3_BIT PA3
|
|
|
|
|
|
|
|
|
|
|
|
void motor_on(void) {
|
|
|
|
// Start turning the motor
|
|
|
|
SET_BIT(FEED_PORT, FEED_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
void motor_off(void) {
|
|
|
|
// Stop turning the motor
|
|
|
|
CLEAR_BIT(FEED_PORT, FEED_BIT);
|
|
|
|
}
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
void dispense(unsigned int ms) {
|
2016-10-08 21:39:50 -07:00
|
|
|
// Activate motor for X ms
|
|
|
|
motor_on();
|
|
|
|
while(ms > 0) {
|
|
|
|
_delay_ms(15);
|
|
|
|
ms -= 15;
|
|
|
|
}
|
|
|
|
motor_off();
|
|
|
|
}
|
|
|
|
|
|
|
|
void pwr_on(void) {
|
|
|
|
// Enable power to the motor control and input dial
|
|
|
|
SET_BIT(PWRON_PORT, PWRON_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
void pwr_off(void) {
|
|
|
|
// Disable power to the motor control and input dial
|
|
|
|
CLEAR_BIT(PWRON_PORT, PWRON_BIT);
|
|
|
|
}
|
|
|
|
|
|
|
|
void bootpulse(void) {
|
|
|
|
// Pulse the motor quickly
|
|
|
|
motor_on();
|
|
|
|
_delay_ms(WAKEUP_PULSE);
|
|
|
|
motor_off();
|
|
|
|
_delay_ms(WAKEUP_BETWEEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
void wakeup(void) {
|
2019-10-18 18:13:05 -07:00
|
|
|
// Notify the user the device is alive
|
2016-10-08 21:39:50 -07:00
|
|
|
motor_on();
|
2019-10-18 18:13:05 -07:00
|
|
|
for(int i=0;i<NUM_PULSES;i++) bootpulse();
|
2016-10-08 21:39:50 -07:00
|
|
|
motor_off();
|
|
|
|
}
|
|
|
|
|
|
|
|
void delay_s(int seconds) {
|
|
|
|
while(seconds-- > 0) {
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
|
|
_delay_ms(100);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t get_speed() {
|
|
|
|
// Get speed setting, 0-15 from 4 pins
|
|
|
|
uint8_t speed = 0;
|
|
|
|
speed |= (IS_SET(NIBBLE3_PORT,NIBBLE3_BIT));
|
|
|
|
speed |= (IS_SET(NIBBLE2_PORT,NIBBLE2_BIT) << 1);
|
|
|
|
speed |= (IS_SET(NIBBLE1_PORT,NIBBLE1_BIT) << 2);
|
|
|
|
speed |= (IS_SET(NIBBLE0_PORT,NIBBLE0_BIT) << 3);
|
2019-10-18 18:13:05 -07:00
|
|
|
#ifdef __DEBUG__
|
|
|
|
dbg_putstring("read setting: ");
|
|
|
|
dbg_putint(speed);
|
|
|
|
dbg_putstring("\r\n");
|
|
|
|
#endif
|
2016-10-08 21:39:50 -07:00
|
|
|
return speed;
|
|
|
|
}
|
|
|
|
|
|
|
|
void setup() {
|
|
|
|
// PART 1: setup io pins
|
|
|
|
// Set motor pin as output
|
|
|
|
SET_BIT(FEED_DDR, FEED_BIT);
|
|
|
|
// Set button pin as input
|
|
|
|
CLEAR_BIT(FORCE_DDR, FORCE_BIT);
|
|
|
|
// Set speed control pins as input
|
|
|
|
CLEAR_BIT(NIBBLE0_DDR, NIBBLE0_BIT);
|
|
|
|
CLEAR_BIT(NIBBLE1_DDR, NIBBLE1_BIT);
|
|
|
|
CLEAR_BIT(NIBBLE2_DDR, NIBBLE2_BIT);
|
|
|
|
CLEAR_BIT(NIBBLE3_DDR, NIBBLE3_BIT);
|
|
|
|
|
|
|
|
// PART 2: Set up interupts
|
|
|
|
cli(); //disable global interrupts
|
|
|
|
|
|
|
|
// Set ISC01= 1 and ISC00 = 0 to generate an external interrupt request
|
2019-10-18 18:13:05 -07:00
|
|
|
// on any change of INT0 (which is on PB2 on an attiny84)
|
2016-10-08 21:39:50 -07:00
|
|
|
SET_BIT(MCUCR,ISC00);
|
|
|
|
SET_BIT(MCUCR,ISC01);
|
2019-10-18 18:13:05 -07:00
|
|
|
// enable external interrupt request 0
|
2016-10-08 21:39:50 -07:00
|
|
|
SET_BIT(GIMSK,INT0);
|
|
|
|
|
|
|
|
// disable ADC (saves power)
|
|
|
|
ADCSRA = 0;
|
|
|
|
|
|
|
|
// configure sleep mode
|
|
|
|
WDTCSR |= (_BV(WDCE) | _BV(WDE)); // Enable the WD Change Bit
|
|
|
|
WDTCSR = _BV(WDIE) | // Enable WDT Interrupt
|
|
|
|
_BV(WDP1) | _BV(WDP2); // Set Timeout to 1 seconds
|
|
|
|
|
|
|
|
//enable global interrupts
|
|
|
|
sei();
|
|
|
|
}
|
|
|
|
|
|
|
|
void gotosleep(void) {
|
|
|
|
// Put the MCU to sleep
|
|
|
|
sleep_bod_disable();
|
|
|
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
|
|
|
sleep_enable();
|
|
|
|
sleep_cpu();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Use to track timing
|
|
|
|
long seconds_since_last_spin = 0;
|
|
|
|
|
|
|
|
// watchdog interrupt
|
|
|
|
ISR (WDT_vect) {
|
|
|
|
// break out of sleep mode
|
|
|
|
sleep_disable();
|
|
|
|
// Increment time counter
|
|
|
|
seconds_since_last_spin += 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
//external interrupt ISR (for INT0 pin)
|
|
|
|
ISR(INT0_vect) {
|
|
|
|
#ifdef __DEBUG__
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putstring("Woken from manual interrupt\r\n");
|
2016-10-08 21:39:50 -07:00
|
|
|
#endif
|
|
|
|
pwr_on();
|
|
|
|
while(IS_SET(FORCE_PORT, FORCE_BIT)) {
|
|
|
|
motor_on();
|
|
|
|
}
|
|
|
|
motor_off();
|
|
|
|
seconds_since_last_spin = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
int main(void) {
|
|
|
|
// Reset all pin configs
|
|
|
|
DDRA = 0b00000000;
|
|
|
|
DDRB = 0b00000000;
|
|
|
|
|
|
|
|
#ifdef __DEBUG__
|
|
|
|
serial_configure();
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putstring(".\r\n\r\n\r\n\r\n\r\nCatFeeder v1.2 Booted!\r\n = ^_^ =\r\n\r\n");
|
2016-10-08 21:39:50 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// Configure the mcu
|
|
|
|
setup();
|
|
|
|
|
|
|
|
// Wiggle so the user knows we're alive
|
|
|
|
pwr_on();
|
|
|
|
wakeup();
|
|
|
|
pwr_off();
|
|
|
|
delay_s(1);
|
|
|
|
|
|
|
|
while (1) {
|
|
|
|
if (seconds_since_last_spin % 10 == 0) {
|
2019-10-18 18:13:05 -07:00
|
|
|
// Power on the input dial, delay while it warms up
|
2016-10-08 21:39:50 -07:00
|
|
|
pwr_on();
|
2019-10-18 18:13:05 -07:00
|
|
|
_delay_ms(1);
|
2016-10-08 21:39:50 -07:00
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// read delay setting, speed can be 0-15
|
2016-10-08 21:39:50 -07:00
|
|
|
uint8_t speed = get_speed();
|
|
|
|
|
2019-10-18 18:13:05 -07:00
|
|
|
// Calculate how long since the last feeding the next one will be
|
|
|
|
long feed_internal = 60L * ((long)BLOCK_BASE + ((long)BLOCK_STEP * (long)speed));
|
|
|
|
|
|
|
|
// This many seconds until the next feeding
|
|
|
|
long to_go = feed_internal - seconds_since_last_spin;
|
2016-10-08 21:39:50 -07:00
|
|
|
|
|
|
|
#ifdef __DEBUG__
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putstring("calculated feed_internal: ");
|
|
|
|
dbg_putlong(feed_internal);
|
|
|
|
dbg_putstring("s\r\n");
|
|
|
|
|
2016-10-08 21:39:50 -07:00
|
|
|
dbg_putstring("Mins to sleep: ");
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putint((unsigned int)feed_internal/60);
|
|
|
|
dbg_putstring("\r\n");
|
2016-10-08 21:39:50 -07:00
|
|
|
dbg_putstring("Mins to go: ");
|
|
|
|
dbg_putint((unsigned int)to_go/60);
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putstring("\r\n\n");
|
2016-10-08 21:39:50 -07:00
|
|
|
#endif
|
|
|
|
|
|
|
|
// If it's been long enough, do the thing
|
|
|
|
if(to_go <= 0) {
|
2019-10-18 18:13:05 -07:00
|
|
|
dbg_putstring("dispensing now\r\n");
|
2016-10-08 21:39:50 -07:00
|
|
|
dispense(DISPENSE_MS);
|
|
|
|
seconds_since_last_spin = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Sleep and do it all again tomorrow
|
|
|
|
pwr_off();
|
|
|
|
gotosleep();
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
}
|