Pet feeder logic for AVR chips
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

271 lines
6.7 KiB

  1. #define __DEBUG__ 1
  2. #include <avr/io.h>
  3. #include <util/delay.h>
  4. #include <libs/serialdebug.h>
  5. #include <libs/macros.h>
  6. #include <avr/interrupt.h>
  7. #include <avr/sleep.h>
  8. #include <avr/wdt.h>
  9. /* User settings */
  10. // How long between feedings is calculated as follows:
  11. // BLOCK_BASE + ($setting * BLOCK_STEP)
  12. // All units are minutes.
  13. // $setting is the 0-15 value read from the speed knob
  14. #define BLOCK_BASE 360
  15. #define BLOCK_STEP 27 // I wanted 30 mins but my chip's clock is off
  16. // How long to pour for, in milliseconds
  17. // Cannot be larger than an unsigned int
  18. #define DISPENSE_MS 12000
  19. // When the device boots, it wiggles the motor to notify the user that is is alive.
  20. // These settings control the length and spacing of the pulses, in milliseconds
  21. #define WAKEUP_PULSE 100
  22. #define WAKEUP_BETWEEN 75
  23. #define NUM_PULSES 6
  24. /* Ports configuration */
  25. // Attiny84 pin layout:
  26. // vcc [1 14] gnd
  27. // PB0 [2 13] PA0 input speed_0
  28. // PB1 [3 12] PA1 input speed_1
  29. // rst PB3 [4 11] PA2 input speed_2
  30. // INT/input feed button PB2 [5 10] PA3 input speed_3
  31. // motor control PA7 [6 9] PA4
  32. // debug serial PA6 [7 8] PA5 dial/motor enable
  33. // Motor control
  34. #define FEED_DDR DDRA
  35. #define FEED_PORT PORTA
  36. #define FEED_BIT PA7
  37. // Dial/motor enable
  38. #define PWRON_DDR DDRA
  39. #define PWRON_PORT PORTA
  40. #define PWRON_BIT PA5
  41. // Force-feed input button - must be an interrupt pin
  42. #define FORCE_DDR DDRB
  43. #define FORCE_PORT PINB
  44. #define FORCE_BIT PB2
  45. // Dial inputs - NIBBLE0 is MSB
  46. #define NIBBLE0_DDR DDRA
  47. #define NIBBLE0_PORT PINA
  48. #define NIBBLE0_BIT PA0
  49. #define NIBBLE1_DDR DDRA
  50. #define NIBBLE1_PORT PINA
  51. #define NIBBLE1_BIT PA1
  52. #define NIBBLE2_DDR DDRA
  53. #define NIBBLE2_PORT PINA
  54. #define NIBBLE2_BIT PA2
  55. #define NIBBLE3_DDR DDRA
  56. #define NIBBLE3_PORT PINA
  57. #define NIBBLE3_BIT PA3
  58. void motor_on(void) {
  59. // Start turning the motor
  60. SET_BIT(FEED_PORT, FEED_BIT);
  61. }
  62. void motor_off(void) {
  63. // Stop turning the motor
  64. CLEAR_BIT(FEED_PORT, FEED_BIT);
  65. }
  66. void dispense(unsigned int ms) {
  67. // Activate motor for X ms
  68. motor_on();
  69. while(ms > 0) {
  70. _delay_ms(15);
  71. ms -= 15;
  72. }
  73. motor_off();
  74. }
  75. void pwr_on(void) {
  76. // Enable power to the motor control and input dial
  77. SET_BIT(PWRON_PORT, PWRON_BIT);
  78. }
  79. void pwr_off(void) {
  80. // Disable power to the motor control and input dial
  81. CLEAR_BIT(PWRON_PORT, PWRON_BIT);
  82. }
  83. void bootpulse(void) {
  84. // Pulse the motor quickly
  85. motor_on();
  86. _delay_ms(WAKEUP_PULSE);
  87. motor_off();
  88. _delay_ms(WAKEUP_BETWEEN);
  89. }
  90. void wakeup(void) {
  91. // Notify the user the device is alive
  92. motor_on();
  93. for(int i=0;i<NUM_PULSES;i++) bootpulse();
  94. motor_off();
  95. }
  96. void delay_s(int seconds) {
  97. while(seconds-- > 0) {
  98. for (int i = 0; i < 10; i++)
  99. _delay_ms(100);
  100. }
  101. }
  102. uint8_t get_speed() {
  103. // Get speed setting, 0-15 from 4 pins
  104. uint8_t speed = 0;
  105. speed |= (IS_SET(NIBBLE3_PORT,NIBBLE3_BIT));
  106. speed |= (IS_SET(NIBBLE2_PORT,NIBBLE2_BIT) << 1);
  107. speed |= (IS_SET(NIBBLE1_PORT,NIBBLE1_BIT) << 2);
  108. speed |= (IS_SET(NIBBLE0_PORT,NIBBLE0_BIT) << 3);
  109. #ifdef __DEBUG__
  110. dbg_putstring("read setting: ");
  111. dbg_putint(speed);
  112. dbg_putstring("\r\n");
  113. #endif
  114. return speed;
  115. }
  116. void setup() {
  117. // PART 1: setup io pins
  118. // Set motor pin as output
  119. SET_BIT(FEED_DDR, FEED_BIT);
  120. // Set button pin as input
  121. CLEAR_BIT(FORCE_DDR, FORCE_BIT);
  122. // Set speed control pins as input
  123. CLEAR_BIT(NIBBLE0_DDR, NIBBLE0_BIT);
  124. CLEAR_BIT(NIBBLE1_DDR, NIBBLE1_BIT);
  125. CLEAR_BIT(NIBBLE2_DDR, NIBBLE2_BIT);
  126. CLEAR_BIT(NIBBLE3_DDR, NIBBLE3_BIT);
  127. // PART 2: Set up interupts
  128. cli(); //disable global interrupts
  129. // Set ISC01= 1 and ISC00 = 0 to generate an external interrupt request
  130. // on any change of INT0 (which is on PB2 on an attiny84)
  131. SET_BIT(MCUCR,ISC00);
  132. SET_BIT(MCUCR,ISC01);
  133. // enable external interrupt request 0
  134. SET_BIT(GIMSK,INT0);
  135. // disable ADC (saves power)
  136. ADCSRA = 0;
  137. // configure sleep mode
  138. WDTCSR |= (_BV(WDCE) | _BV(WDE)); // Enable the WD Change Bit
  139. WDTCSR = _BV(WDIE) | // Enable WDT Interrupt
  140. _BV(WDP1) | _BV(WDP2); // Set Timeout to 1 seconds
  141. //enable global interrupts
  142. sei();
  143. }
  144. void gotosleep(void) {
  145. // Put the MCU to sleep
  146. sleep_bod_disable();
  147. set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  148. sleep_enable();
  149. sleep_cpu();
  150. }
  151. // Use to track timing
  152. long seconds_since_last_spin = 0;
  153. // watchdog interrupt
  154. ISR (WDT_vect) {
  155. // break out of sleep mode
  156. sleep_disable();
  157. // Increment time counter
  158. seconds_since_last_spin += 1;
  159. }
  160. //external interrupt ISR (for INT0 pin)
  161. ISR(INT0_vect) {
  162. #ifdef __DEBUG__
  163. dbg_putstring("Woken from manual interrupt\r\n");
  164. #endif
  165. pwr_on();
  166. while(IS_SET(FORCE_PORT, FORCE_BIT)) {
  167. motor_on();
  168. }
  169. motor_off();
  170. seconds_since_last_spin = 0;
  171. }
  172. int main(void) {
  173. // Reset all pin configs
  174. DDRA = 0b00000000;
  175. DDRB = 0b00000000;
  176. #ifdef __DEBUG__
  177. serial_configure();
  178. dbg_putstring(".\r\n\r\n\r\n\r\n\r\nCatFeeder v1.2 Booted!\r\n = ^_^ =\r\n\r\n");
  179. #endif
  180. // Configure the mcu
  181. setup();
  182. // Wiggle so the user knows we're alive
  183. pwr_on();
  184. wakeup();
  185. pwr_off();
  186. delay_s(1);
  187. while (1) {
  188. if (seconds_since_last_spin % 10 == 0) {
  189. // Power on the input dial, delay while it warms up
  190. pwr_on();
  191. _delay_ms(1);
  192. // read delay setting, speed can be 0-15
  193. uint8_t speed = get_speed();
  194. // Calculate how long since the last feeding the next one will be
  195. long feed_internal = 60L * ((long)BLOCK_BASE + ((long)BLOCK_STEP * (long)speed));
  196. // This many seconds until the next feeding
  197. long to_go = feed_internal - seconds_since_last_spin;
  198. #ifdef __DEBUG__
  199. dbg_putstring("calculated feed_internal: ");
  200. dbg_putlong(feed_internal);
  201. dbg_putstring("s\r\n");
  202. dbg_putstring("Mins to sleep: ");
  203. dbg_putint((unsigned int)feed_internal/60);
  204. dbg_putstring("\r\n");
  205. dbg_putstring("Mins to go: ");
  206. dbg_putint((unsigned int)to_go/60);
  207. dbg_putstring("\r\n\n");
  208. #endif
  209. // If it's been long enough, do the thing
  210. if(to_go <= 0) {
  211. dbg_putstring("dispensing now\r\n");
  212. dispense(DISPENSE_MS);
  213. seconds_since_last_spin = 0;
  214. }
  215. }
  216. // Sleep and do it all again tomorrow
  217. pwr_off();
  218. gotosleep();
  219. }
  220. return 0;
  221. }