// simon.c - A simon says clone // (c) 2010 me@nikosapi.org GPLv2 #include #include #include #include #include #include /* See simon.h for configurable paramerters */ #include "simon.h" // A mask of all the pins connected to a light, useful // for turning all the lights on or off at once. #define LIGHTS_MASK ( _BV(LIGHTS[0]) | _BV(LIGHTS[1]) | \ _BV(LIGHTS[2]) | _BV(LIGHTS[3]) ) // The formula for calculating how long each level is #define puzzle_length(level) (3 + (level / 4)) // A lookup table which translates a number from 0 to 3 // into the numerical value for one of the 4 pins const uint8_t LIGHTS[] = LIGHTS_PINS; volatile uint8_t PUZZLE_LEVEL = 0; // The current game number uint8_t EEMEM _PUZZLE_LEVEL = 0; // Non-volatile storage for the game number uint8_t PUZZLE[puzzle_length(0xff)]; // The puzzle array uint8_t *PUZZLE_PTR = PUZZLE; // Pointer to the current puzzle item // Puzzle status flags // PUZZLE_START: Set when the device powers up for the first time // PUZZLE_RESUME: Set when the device wakes up from sleep // PUZZLE_RESET: Set when the reset key combination is pressed // PUZZLE_PASS: Set when the user successfully completes a puzzle // PUZZLE_FAIL: Set when the user enters a wrong value enum { PUZZLE_START, PUZZLE_RESUME, PUZZLE_RESET, PUZZLE_PASS, PUZZLE_FAIL }; volatile uint8_t PUZZLE_STATUS; // Keep track of the puzzle status // Used to keep track of how many times Timer1 has overflowed volatile uint8_t SLEEP_TIMER; volatile uint16_t LFSR; // The random number generator's current state volatile uint16_t PUZZLE_SEED; // Copy of LFSR from before the current puzzle uint16_t EEMEM _PUZZLE_SEED = 0; // Non-volatile storage for PUZZLE_SEED /********************************************************** * Random Number Generator * **********************************************************/ /* Returns a random number using a linear feedback shift register. NOTE: LFSR must NOT be 0 */ uint16_t lfsr_rand() { uint16_t bit = ((LFSR>>0) ^ (LFSR>>2) ^ (LFSR>>3) ^ (LFSR>>5)) & 1; LFSR = (bit<<15) | (LFSR>>1); return LFSR; } // Load the LFSR state from eeprom void lfsr_load() { uint16_t seed = eeprom_read_word(&_PUZZLE_SEED); if (seed) PUZZLE_SEED = LFSR = seed; else // only happens after the first power-on after a flash lfsr_load_random_seed(); } // Use get_random_seed() to generate an initial value for the LFSR void lfsr_load_random_seed() { // Get a somewhat random number from the ADC uint16_t seed = get_random_seed(); // PUZZLE_SEED must be loaded with seed asap or timer might overwrite it PUZZLE_SEED = LFSR = seed; for (uint8_t i = 0; i<50; i++) lfsr_rand(); } // Save the LFSR state to eeprom void lfsr_save() { eeprom_busy_wait(); if (eeprom_read_word(&_PUZZLE_SEED) != PUZZLE_SEED){ eeprom_busy_wait(); eeprom_write_word(&_PUZZLE_SEED, PUZZLE_SEED); } } /* Generates a random puzzle using lfsr_rand() and loads it into PUZZLE. */ void lfsr_generate_puzzle(uint8_t length) { PUZZLE_SEED = LFSR; PUZZLE_PTR = PUZZLE; while (length--) *PUZZLE_PTR++ = _BV(lfsr_rand() % 4); *PUZZLE_PTR = 0xf0; // This marks the end of a puzzle PUZZLE_PTR = PUZZLE; // Reset the puzzle pointer } /* Use the crappy temperature sensor in the ATtiny45 to generate a somewhat random number. */ uint16_t get_random_seed() { uint16_t result = 0; PRR &= ~_BV(PRADC); // Power-up the ADC ADMUX |= _BV(REFS1); // 1.1V reference ADMUX |= _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // temp sensor ADCSRA = _BV(ADEN); // Enable the ADC, Prescaler of 2 for maximum noise for (int i=0; i<32; i++){ ADCSRA |= _BV(ADSC); // Start the ADC loop_until_bit_is_set( ADCSRA, ADIF ); // Wait for the ADC to complete result += ADCL; // Get the low bits of the result result += ADCH<<8; // Add the two high bits } ADMUX = 0; ADCSRA = 0; PRR |= _BV(PRADC); // Turn off the ADC to save power return result; } /********************************************************** * Sleep Timer * **********************************************************/ void enable_sleep_timer() { PRR &= ~_BV(PRTIM1); // Power-up Timer1 // Set prescaler to 16384 (dividing 1MHz internal clock) TCCR1 = _BV(CS13) | _BV(CS12) | _BV(CS11) | _BV(CS10); TIMSK = _BV(TOIE1); // Enable the overflow interrupt } void disable_sleep_timer() { TIMSK &= ~_BV(TOIE1); // Turn off overflow interrupt TCCR1 = 0; // Set pre-scaler to "stopped" PRR |= _BV(PRTIM1); // Power-down Timer1 } /* Bring the microcontroller into it's lowest power state */ void power_down() { disable_sleep_timer(); // Save some power sei(); // We'll need this to wake up set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_mode(); // Power off enable_sleep_timer(); } /********************************************************** * Display Code * **********************************************************/ /* This function toggles the outputs specified by "lights_mask" (a number from 0x00 to 0x0f). The duty cycle can be adjusted by using the "on_ms" and "off_ms" arguments. The "count" argument dictates how many times to blink the lights. The running time of this function is: on_ms*count + off_ms*(count-1) This function takes care of setting the port's pins in the output direction then sets them back to inputs when it's done. External interrupts should be disabled before running this function. */ void _blink_lights( uint8_t lights_mask, uint16_t on_ms, uint16_t off_ms, uint8_t count ) { LIGHTS_DIR |= LIGHTS_MASK; while (count--){ LIGHTS_PORT |= lights_mask; delay_ms(on_ms); LIGHTS_PORT &= ~lights_mask; if (count) delay_ms(off_ms); } LIGHTS_DIR &= ~LIGHTS_MASK; } void blink_lights( uint8_t lights_mask, uint16_t on_ms, uint16_t off_ms, uint8_t count ) { _blink_lights( real_mask(lights_mask), on_ms, off_ms, count ); } /* A delay loop that doesn't require floating point math for non-constant numbers (like _delay_ms). It's not as accurate as _delay_ms, but close enough. */ void delay_ms( uint16_t ms ) { while (ms--) _delay_loop_2(250); } /* Masks for the blink_lights function are simple 4 bit numbers with each bit representing one light. This function correctly matches a bit with the LIGHTS_PINS table. */ uint8_t real_mask( uint8_t mask ) { uint8_t out_mask = 0; for (int i=0; i<4; i++){ out_mask |= (mask & 1)<>= 1; } return out_mask; } /* This function displays the current puzzle to the user. */ void display_puzzle() { blink_lights( 0x0f, 200, 100, 3 ); _delay_ms(300); PUZZLE_PTR = PUZZLE; while ( *PUZZLE_PTR != 0xf0 ){ blink_lights( *PUZZLE_PTR++, 600, 0, 1 ); _delay_ms(300); } _delay_ms(300); blink_lights( 0x0f, 200, 100, 3 ); PUZZLE_PTR = PUZZLE; } /********************************************************** * Initialization & Main Loop * **********************************************************/ void _init() { // Save some power PRR |= _BV(PRTIM0); // Disable Timer0 PRR |= _BV(PRUSI); // Disable USI PRR |= _BV(PRADC); // Disable the ADC // Set up pin change interrupts PCMSK = LIGHTS_MASK; GIMSK = _BV(PCIE); enable_sleep_timer(); // Set up the sleep timer } int main() { _init(); // Run initialization routines PUZZLE_STATUS = _BV(PUZZLE_START); // Main loop while(1){ if (! PUZZLE_STATUS) // nothing to do, loop continue; cli(); // Disable interrupts while blinking lights _delay_ms(500); // The device has just been powered on if (bit_is_set(PUZZLE_STATUS, PUZZLE_START)){ // Load the previous progress PUZZLE_LEVEL = eeprom_read_byte(&_PUZZLE_LEVEL); lfsr_load(); // load the random number generator's state // Generate the first puzzle lfsr_generate_puzzle(puzzle_length(PUZZLE_LEVEL)); } if (bit_is_set(PUZZLE_STATUS, PUZZLE_RESET)){ lfsr_generate_puzzle(puzzle_length(PUZZLE_LEVEL)); } // The user has successfully completed the current puzzle if (bit_is_set(PUZZLE_STATUS, PUZZLE_PASS)){ PUZZLE_LEVEL++; blink_lights( 0x0f, 500, 500, 2 ); // The success light show lfsr_generate_puzzle(puzzle_length(PUZZLE_LEVEL)); } // The user messed up so they are presented with the same puzzle if (bit_is_set(PUZZLE_STATUS, PUZZLE_FAIL)) blink_lights( 0x0f, 5000, 0, 1 ); // The fail light show _delay_ms(1000); display_puzzle(); // This takes care of resetting the puzzle pointer PUZZLE_STATUS = 0; sei(); } } /********************************************************** * Interrupt Handlers * **********************************************************/ /* Pin changed interrupt handler. Checks if the user entered the right input and sets the appropriate PUZZLE_STATUS flags */ ISR(PCINT0_vect) { _delay_ms(20); // Wait for some debounce uint8_t pins = LIGHTS_PIN; if (pins == RESET_MASK){ _blink_lights( RESET_MASK ^ LIGHTS_MASK, 100, 100, 15 ); lfsr_load_random_seed(); PUZZLE_LEVEL = 0; PUZZLE_STATUS = _BV(PUZZLE_RESET); return; } // Don't continue if this gets called right after a resume if ( (LIGHTS_MASK & pins) && bit_is_clear(PUZZLE_STATUS, PUZZLE_RESUME)){ if ( pins != real_mask(*PUZZLE_PTR) ){ PUZZLE_STATUS = _BV(PUZZLE_FAIL); // wrong button, fail. } if (*(++PUZZLE_PTR) == 0xf0) // puzzle is complete. PUZZLE_STATUS = _BV(PUZZLE_PASS); } SLEEP_TIMER = 0; // Reset the sleep timer every time a button is pressed } /* Timer1 overflow interrupt handler. This runs about every 4.2 seconds (with F_CPU equal to 1MHz). It simply increments SLEEP_TIMER and calls power_down() when it has grown large enough (if it's equal to SLEEP_TIMEOUT). Before powering down it will set the PUZZLE_RESUME flag in PUZZLE_STATUS. */ ISR(TIM1_OVF_vect) { SLEEP_TIMER++; lfsr_save(); if (eeprom_read_byte(&_PUZZLE_LEVEL) != PUZZLE_LEVEL){ eeprom_busy_wait(); eeprom_write_byte(&_PUZZLE_LEVEL, PUZZLE_LEVEL); } if (SLEEP_TIMER >= SLEEP_TIMEOUT){ PUZZLE_STATUS = _BV(PUZZLE_RESUME); power_down(); } }