/* 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_SECS 60 // You can change VMAX, the max number of chars in array v. #define VMAX 80 #define MORSE_TICKS MILLIS(333) // 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) byte inactive_secs; byte inactive; byte active; enum State { MU_START, MU_RECITE, MU_GAP, MU_RABBIT_RIGHT, MU_CLICKED, MU_BAD, MU_MU, MU_AWAKEN, BF_START, BF_GAP, BF_CLICKED, BF_EDIT, BF_RECITE, BF_RUN }; byte state; byte arg; // Some states use arg. byte tick; // How long in state and arg. byte clicks; // Count number of button presss. byte morse[5]; // Was pulse long? byte vlen; // Length of v string. byte v[VMAX]; // Vector holding string, e.g. {M, I, U} byte tape[VMAX]; byte ptr; enum Token { TNONE, // 0 ... TRIGHT, // > 1 ..- TPLUS, // + 2 .-. TCLOSE, // ] 3 .-- TLEFT, // < 4 -.. TMINUS, // - 5 -.- TOPEN, // [ 6 --. TPRINT, // . 7 --- }; void show(byte m, byte i, byte u) { // LEDs Outputs are Active Low. LED(M, m); LED(I, i); LED(U, u); } void showX(byte x) { show(x&4, x&2, x&1); } #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 { showX(clicks); } } #define TICKS_BEEP MILLIS(100) void bad() { // Blink thrice. tick=0,arg=MILLIS(600),state=MU_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=MU_RABBIT_RIGHT; show0(); #if 1 // BF: vlen = 3; v[0] = TPLUS; v[1] = TPLUS; v[2] = TPRINT; vlen=5; v[0]=TPLUS; v[1]=TOPEN; v[2]=TPRINT; v[3]=TPLUS; v[4]=TCLOSE; state = BF_RECITE; #endif } //////////////////////////////////////////////////////// /* 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_secs = 0; inactive = 0; } else { ++inactive; active = 0; if (inactive >= MILLIS(1000)) { ++inactive_secs; inactive = 0; } } // Check for inactivity, and power down if so. if (inactive_secs > INACTIVE_SECS) { 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_secs = 0; 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==MU_RECITE && vlen==2 && v[0]==M && v[1]==U) { state=MU_MU; // Special state for blinky show MU. } if (down) { showButtonCount(); if (active == 1) { // Once per click. morse[clicks] = 0; ++clicks; } if (active > MORSE_TICKS) { morse[clicks-1] = 1; } tick=0,arg=0; state= state < BF_START ? MU_CLICKED : BF_CLICKED; if (active > MILLIS(2000)) { // Reset after 2.0 sec reset(); } } else switch (state) { case MU_START: reset(); break; case MU_CLICKED: #define TICKS_CLICKED MILLIS(800) if (tick < TICKS_CLICKED) { 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 MU_BAD, continue at MU_GAP. if (state != MU_BAD) { tick=0, arg=MILLIS(800), state=MU_GAP; } } break; case MU_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=MU_GAP; } break; case MU_GAP: // arg is how many ticks. if (tick < arg) { show0(); } else { tick=0,arg=0,state=MU_RECITE; } break; case MU_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=MU_GAP; } } break; case MU_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=MU_GAP; } break; case MU_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=MU_GAP; } break; case BF_RECITE: if (arg >= vlen) { tick=0,arg=MILLIS(1500),state=BF_GAP; } else if (tick < TICKS_ON) { showX(v[arg]); } else if (tick < TICKS_ON+TICKS_OFF) { show0(); } else { tick = 0; ++arg; } break; case BF_GAP: // arg is how many ticks. if (tick < arg) { show0(); } else { tick=0,arg=0,state=BF_RECITE; } break; case BF_RUN: // arg is program counter. if (arg >= vlen) { arg = MILLIS(500), state = BF_GAP; } else { showX(0); delay(100); showX(v[arg]); delay(30); showX(0); delay(100); switch(v[arg]) { case TPLUS: tape[ptr]++; break; case TMINUS: tape[ptr]--; break; case TLEFT: if (ptr > 0) ptr--; break; case TRIGHT: if (ptr < VMAX-1) ptr++; break; case TPRINT: showX(0); delay(100); showX(tape[ptr]); delay(1000); showX(0); delay(100); break; case TOPEN: if (!tape[ptr]) { int depth = 1; ++arg; while (arg < vlen) { if (v[arg] == TCLOSE) { --depth; if (!depth) break; } else if (v[arg] == TOPEN) { ++depth; } ++arg; }; } break; case TCLOSE: if (!tape[ptr]) { int depth = 1; --arg; while (arg < vlen) { if (v[arg] == TOPEN) { --depth; if (!depth) break; } else if (v[arg] == TCLOSE) { ++depth; } --arg; }; if (depth==0 && arg < vlen) { --arg; // Counteract the ++arg below. } } break; } ++arg; } break; case BF_CLICKED: if (tick < TICKS_CLICKED) { showButtonCount(); } else { switch(clicks) { case 1: for (int i = 0; i < VMAX; i++) tape[i] = 0; ptr = 0; arg = 0; // arg is program counter state = BF_RUN; break; case 2: tick=0, arg=MILLIS(500), state=BF_GAP; break; case 3: // Append 3-code. if (vlen < VMAX && (morse[0] || morse[1] || morse[2])) { v[vlen] = (morse[0]<<2) | (morse[1]<<1) | morse[2]; ++vlen; } tick=0, arg=MILLIS(500), state=BF_GAP; break; case 4: tick=0, arg=MILLIS(500), state=BF_GAP; break; case 5: // Clear BF. vlen = 0; break; default: tick=0, arg=MILLIS(500), state=BF_GAP; } clicks=0; // Reset number or clicks after production. } 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(); reset(); }