avr-petfeeder/main.c

271 lines
6.7 KiB
C

#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>
/* User settings */
// 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
#define BLOCK_BASE 360
#define BLOCK_STEP 27 // I wanted 30 mins but my chip's clock is off
// How long to pour for, in milliseconds
// Cannot be larger than an unsigned int
#define DISPENSE_MS 12000
// 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
/* 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
#define FEED_DDR DDRA
#define FEED_PORT PORTA
#define FEED_BIT PA7
// Dial/motor enable
#define PWRON_DDR DDRA
#define PWRON_PORT PORTA
#define PWRON_BIT PA5
// Force-feed input button - must be an interrupt pin
#define FORCE_DDR DDRB
#define FORCE_PORT PINB
#define FORCE_BIT PB2
// Dial inputs - NIBBLE0 is MSB
#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);
}
void dispense(unsigned int ms) {
// 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) {
// Notify the user the device is alive
motor_on();
for(int i=0;i<NUM_PULSES;i++) bootpulse();
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);
#ifdef __DEBUG__
dbg_putstring("read setting: ");
dbg_putint(speed);
dbg_putstring("\r\n");
#endif
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
// on any change of INT0 (which is on PB2 on an attiny84)
SET_BIT(MCUCR,ISC00);
SET_BIT(MCUCR,ISC01);
// enable external interrupt request 0
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__
dbg_putstring("Woken from manual interrupt\r\n");
#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();
dbg_putstring(".\r\n\r\n\r\n\r\n\r\nCatFeeder v1.2 Booted!\r\n = ^_^ =\r\n\r\n");
#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) {
// Power on the input dial, delay while it warms up
pwr_on();
_delay_ms(1);
// read delay setting, speed can be 0-15
uint8_t speed = get_speed();
// 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;
#ifdef __DEBUG__
dbg_putstring("calculated feed_internal: ");
dbg_putlong(feed_internal);
dbg_putstring("s\r\n");
dbg_putstring("Mins to sleep: ");
dbg_putint((unsigned int)feed_internal/60);
dbg_putstring("\r\n");
dbg_putstring("Mins to go: ");
dbg_putint((unsigned int)to_go/60);
dbg_putstring("\r\n\n");
#endif
// If it's been long enough, do the thing
if(to_go <= 0) {
dbg_putstring("dispensing now\r\n");
dispense(DISPENSE_MS);
seconds_since_last_spin = 0;
}
}
// Sleep and do it all again tomorrow
pwr_off();
gotosleep();
}
return 0;
}