/* mu002: output pins now active low */ /* vim: sw=2:ts=2:ai */ // You can change TICKS_PER_SEC; 20 or 40 seems good. #define TICKS_PER_SEC 20 #define TICK_MS (1000 / TICKS_PER_SEC) #define MILLIS(millis) ((int)((millis)/TICK_MS)) // You can change INACTIVE_SEC, the time until hibernation. #define INACTIVE_TICKS MILLIS(20000) // You can change VMAX, the max number of chars in array v. #define VMAX 80 // Define I/O ports (on port PB) #define RESET 5 /* no connection to RESET; pull internally high. */ #define BUTTON 2 /* momentary pushbutton SWITCH; active LOW. */ #define M 0 /* 1st LED output; active (LIT) on LOW. */ #define I 3 /* 2nd LED output; active (LIT) on LOW. */ #define U 4 /* 3rd LED output; active (LIT) on LOW. */ // Avoid PB1: it inteferes with reprogramming the chip. // Get input switch pin, active LOW. i.e. TRUE if LOW. #define SWITCH(PIN) (digitalRead(PIN) == LOW) // Put output LED, active LOW. i.e. Lit up if LOW. #define LED(PIN, LIT) digitalWrite((PIN), (LIT) ? LOW : HIGH) int inactive; int active; enum State { S_START, S_RECITE, S_GAP, S_RABBIT_RIGHT, S_CLICKED, S_BAD, S_MU, S_AWAKEN }; byte state; int arg; // Some states use arg. int tick; // How long in state and arg. byte clicks; // Count number of button presss. byte vlen; // Length of v string. byte v[VMAX]; // Vector holding string, e.g. {M, I, U} void show(byte m, byte i, byte u) { // LEDs Outputs are Active Low. LED(M, m); LED(I, i); LED(U, u); } #define showM() show(1,0,0) #define showI() show(0,1,0) #define showU() show(0,0,1) #define show0() show(0,0,0) void showButtonCount() { if (clicks > 5) clicks = 5; // Clamp at 5 clicks. if ((active+tick)&1) { show(0,0,0); // Make button feedback flashy. } else { show(clicks&4, clicks&2, clicks&1); } } #define TICKS_BEEP MILLIS(100) void bad() { // Blink thrice. tick=0,arg=MILLIS(600),state=S_BAD; } void copy(int n, int from, int to) { for (int i = 0; i < n; i++) { v[to+i] = v[from+i]; } } void production1() { if (vlen >= VMAX) { bad(); } else if (vlen < 1) { bad(); } else if (v[vlen-1] != I) { bad(); } else { v[vlen]=U; vlen++; } } void production2() { int p; for (p=0; p VMAX) { bad(); } else { copy(c, p+1, vlen); vlen += c; } } } void production3() { int p; for (p=0; p=vlen-2) { bad(); } else { v[p] = U; copy(vlen-p-2, p+3, p+1); vlen -= 2; } } void production4() { int p; for (p=0; p=vlen-1) { bad(); } else { copy(vlen-p-2, p+2, p); vlen -= 2; } } void production5() { if (vlen < 2) { bad(); } else { byte t = v[0]; v[0] = v[1]; v[1] = t; } } void reset() { vlen = 2; v[0] = M; v[1] = I; clicks=0; tick=0,arg=3,state=S_RABBIT_RIGHT; show0(); } //////////////////////////////////////////////////////// /* Thanks: http://www.liubo.us/how-to-make-attiny85-sleep-and-save-power/ 1. This code assumes a momentary contact switch from PB2/INT0 to ground, this is used to wake the MCU. 2. Before calling goToSleep(), to minimize current consumption,ensure that output pins are set high or low as needed so that their loads do not draw current, and all other pins are set as inputs with the pull-up resistor enabled. 3. The 5V regulator will continue to draw quiescent current even if the AVR is in power-down mode, which defeats the whole purpose. A 7805 will typically draw 6mA even if it’s not powering a load, LDO types can do quite a bit better. */ #include #include #define BODS 7 //BOD Sleep bit in MCUCR #define BODSE 2 //BOD Sleep enable bit in MCUCR uint8_t mcucr1, mcucr2; void hibernate(void) { GIMSK |= _BV(INT0); //enable INT0 MCUCR &= ~(_BV(ISC01) | _BV(ISC00)); //INT0 on low level ACSR |= _BV(ACD); //disable the analog comparator ADCSRA &= ~_BV(ADEN); //disable ADC set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); //turn off the brown-out detector. //must have an ATtiny45 or ATtiny85 rev C or later for software to be able to disable the BOD. //current while sleeping will be <0.5uA if BOD is disabled, <25uA if not. cli(); mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector mcucr2 = mcucr1 & ~_BV(BODSE); MCUCR = mcucr1; MCUCR = mcucr2; sei(); //ensure interrupts enabled so we can wake up again sleep_cpu(); //go to sleep cli(); //wake up here, disable interrupts GIMSK = 0x00; //disable INT0 sleep_disable(); sei(); //enable interrupts again (but INT0 is disabled from above) } ISR(INT0_vect) {} //nothing to actually do here, the interrupt just wakes us up! //////////////////////////////////////////////////////// void loop() { byte down = SWITCH(BUTTON); // Keep track of active & inactive times. if (down) { ++active; inactive = 0; } else { ++inactive; active = 0; } // Check for inactivity, and power down if so. if (inactive > INACTIVE_TICKS) { for (arg= 0; arg < MILLIS(1000); arg++) { show( (arg%3)==0, (arg%3)==1, (arg%3)==2 ); // Blinky delay(TICK_MS); ++arg; } show0(); pinMode(M, INPUT_PULLUP); pinMode(I, INPUT_PULLUP); pinMode(U, INPUT_PULLUP); hibernate(); pinMode(M, OUTPUT); pinMode(I, OUTPUT); pinMode(U, OUTPUT); active = inactive = 0; // Stay here until button is released. arg = 0; while (SWITCH(BUTTON)) { show( (arg%3)==0, (arg%3)==1, (arg%3)==2 ); // Blinky delay(TICK_MS); ++arg; } reset(); } // Check for victory. if (state==S_RECITE && vlen==2 && v[0]==M && v[1]==U) { state=S_MU; // Special state for blinky show MU. } if (down) { showButtonCount(); if (active == 1) { // Once per click. ++clicks; } tick=0,arg=0,state=S_CLICKED; if (active > MILLIS(1600)) { // Reset after 1.6 sec reset(); } } else switch (state) { case S_START: reset(); break; case S_CLICKED: #define TICKS_CLICKED MILLIS(800) if (tick < TICKS_CLICKED) { // show(clicks&4, clicks&2, clicks&1); showButtonCount(); } else { switch(clicks) { case 1: production1(); break; case 2: production2(); break; case 3: production3(); break; case 4: production4(); break; case 5: production5(); break; default: bad(); } clicks=0; // Reset number or clicks after production. // If we came out of production without S_BAD, continue at S_GAP. if (state != S_BAD) { tick=0, arg=MILLIS(800), state=S_GAP; } } break; case S_RECITE: // arg must init to 0 #define TICKS_ON MILLIS(500) #define TICKS_OFF MILLIS(200) if (tick < TICKS_ON) { switch (v[arg]) { case M: showM(); break; case I: showI(); break; case U: showU(); break; default: show(1,1,1); // Should not happen. } } else if (tick < TICKS_ON+TICKS_OFF) { show0(); } else { tick = 0; ++arg; if (arg >= vlen) tick=0,arg=MILLIS(1500),state=S_GAP; } break; case S_GAP: // arg is how many ticks. if (tick < arg) { show0(); } else { tick=0,arg=0,state=S_RECITE; } break; case S_RABBIT_RIGHT: // arg is how many rabbits. #define TICKS_RABBIT MILLIS(100) if (tick < 1*TICKS_RABBIT) { showM(); } else if (tick < 2*TICKS_RABBIT) { showI(); } else if (tick < 3*TICKS_RABBIT) { showU(); } else if (tick < 6*TICKS_RABBIT) { show0(); } else { tick = 0; --arg; if (arg < 1) { tick=0,arg=MILLIS(1000),state=S_GAP; } } break; case S_BAD: // arg is how long. if (tick < arg) { if ((tick / TICKS_BEEP) & 1) { show(1,1,1); } else { show(0,0,0); } } else { tick=0,arg=MILLIS(1000),state=S_GAP; } break; case S_MU: // arg is how long. if (tick < MILLIS(700)) { if ((tick/MILLIS(50))&1) show0(); else showM(); } else if (tick < MILLIS(1000)) { show0(); } else if (tick < MILLIS(1000+700)) { if ((tick/MILLIS(50))&1) show0(); else showU(); } else { tick=0,arg=MILLIS(1200),state=S_GAP; } break; } delay(TICK_MS); ++tick; } void setup() { pinMode(RESET, INPUT_PULLUP); // a.k.a. RESET line. pinMode(BUTTON, INPUT_PULLUP); pinMode(M, OUTPUT); pinMode(I, OUTPUT); pinMode(U, OUTPUT); show0(); }