avr-petfeeder/main.c

253 lines
5.5 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
#define HOUR 3600
// I wanted each block of time to be 45 mins, but my chip's clock is slow
// 40 should roughly scale to 45
#define BLOCK_MINS 40
// How long to pour for
#define DISPENSE_MS 12000
/*
vcc [1 14] gnd
[2 13] input speed_0
[3 12] input speed_1
rst [4 11] input speed_2
INT/input feed button [5 10] input speed_3
motor [6 9]
debug serial [7 8]
*/
// Ports
#define FEED_DDR DDRA
#define FEED_PORT PORTA
#define FEED_BIT PA7
#define PWRON_DDR DDRA
#define PWRON_PORT PORTA
#define PWRON_BIT PA5
#define FORCE_DDR DDRB
#define FORCE_PORT PINB
#define FORCE_BIT PB2
#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(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);
}
#define WAKEUP_PULSE 100
#define WAKEUP_BETWEEN 75
void bootpulse(void) {
// Pulse the motor quickly
motor_on();
_delay_ms(WAKEUP_PULSE);
motor_off();
_delay_ms(WAKEUP_BETWEEN);
}
void wakeup(void) {
// Perform
motor_on();
for(int i=0;i<6;i++) bootpulse();
motor_off();
}
void delay_s(int seconds) {
while(seconds-- > 0) {
for (int i = 0; i < 10; i++)
_delay_ms(100);
#ifdef __DEBUG__
dbg_putstring("Wait...\n");
#endif
}
}
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);
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
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("AYE MATEY\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("\n\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) {
pwr_on();
_delay_ms(1); // Let input pins warm up
// speed can be 0-15
// delay = 2hrs + speed * .75h
// range of 1hr to 12hrs25m
uint8_t speed = get_speed();
long sleep_max = HOUR + (BLOCK_MINS * 60 * speed); // 45 mins per unit of speed
long to_go = sleep_max - seconds_since_last_spin;
#ifdef __DEBUG__
dbg_putstring("Mins to sleep: ");
dbg_putint((unsigned int)sleep_max/60);
dbg_putstring("\n");
dbg_putstring("Mins to go: ");
dbg_putint((unsigned int)to_go/60);
dbg_putstring("\n\n");
#endif
// If it's been long enough, do the thing
if(to_go <= 0) {
dispense(DISPENSE_MS);
seconds_since_last_spin = 0;
}
}
// Sleep and do it all again tomorrow
pwr_off();
gotosleep();
}
return 0;
}