#define __DEBUG__ 1 #include #include #include #include #include #include #include /* 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 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; }