Simple Simon

A tiny classic game on MSP430. Key words: PCB, debouncing.

Left: the design schema. Right: the board.

This is a minimal Simon PCB. The button in the middle is reset, while other four correspond to four LEDs in the same order. To play, first, push reset. The LEDs would blink in certain “random” order, starting from one blink, and goes up to twenty blinks. After the blinks, press buttons in the same order as the one LED blinked before. Every five passes would trigger a small blinking pattern as a reward. This is a super low-power device and runs on MSP430G2553.

The “random” order of blinking requires a pseudo-random generator and is achieved by Linear Feedback Shift Register (LFSR). The input of LFSR is a linear combination of its output, which is part of the number itself, most commonly some of its digits; the next phase would be the digits shifted either to left or right, with a new input fed into the head. It takes a few tries to make an LFSR that displays satisfiable “random” behavior.

Another problem would be debouncing the buttons. Most users would have a fast response that they press the buttons with very small time intervals, mostly less than 200ms. The physical structure inside the button determines its instability, and this bouncing feature is enlarged between multiple pressing across the buttons. The simple solution is to disable all buttons for certain time period after the first detection of pressing, say 20ms; this number is also adjustable, it requires multiple groups of tests since the time slots heavily depend on user’s reaction and the condition of buttons. This method has great effects, but cannot promise to work every time. Another approach takes use of the watchdog timer feature in MSP430. When the program enters WDT interrupt, all buttons are disabled until the WDT is reset. This provides a better performance of buttons, but still cannot ensure complete debounce. The ultimate solution is to combine software debouncing with hardware debouncing, that is, connect a 100uF capacitor in parallel with the button. It absorbs the voltage oscillation and provides a smooth on/off transition.

A demo of Simple Simon game made on self-made PCB

Here’s the C source file that implements this idea:

#include <msp430.h>
#define LED1 BIT3
#define LED2 BIT2
#define LED3 BIT1
#define LED4 BIT0
#define BUTTON1 BIT7
#define BUTTON2 BIT4
#define BUTTON3 BIT5
#define BUTTON4 BIT6
int randInt(void);
void LED_config(int rand);
void debounce(void);
void error_config(int record[],int answer[],int *rep);
unsigned int a_mask = 0xABCD;
unsigned int a_gen = 0x1234;
#define poly_mask 0xB4BC
#define poly_gen 0x7A5B
void bonus_config(void);
int count;
int rec[69];
int ans[69];
// This is not arbitrary; oversize arrays would cause unpredictable behaviors

void main(void) {
    int r;
    WDTCTL = WDTPW | WDTHOLD;	// Stop watchdog timer
    BCSCTL3 |= LFXT1S_2;	// Turn on VLO mode. Clock frequency 32768Hz.
	P1DIR |= LED1 + LED2 + LED3 + LED4;	// Set direction as out for LEDs
	// Port1 selected as button input.
        // P1SELx cannot be set because interrupt should be enabled
	P1REN |= BUTTON1 + BUTTON2 + BUTTON3 + BUTTON4;	// Set pull-up resistor
	P1OUT |= BUTTON1 + BUTTON2 + BUTTON3 + BUTTON4;	// Ac tive low, set P1.3 as 1
	P1IFG &= ~(BUTTON1 + BUTTON2 + BUTTON3 + BUTTON4);	// P1.3 IFG cleared
	for (r = 0; r < 70 ; r++) {	// Playing game over and over again
	    int t;
	    count = 0;
            for (t = 0; t <= r; t++){
            int num = randInt();	// Generate random number
            rec[t] = num;
            LED_config(num);	// Flash the LED
        }
        P1IE |= (BUTTON1 + BUTTON2 + BUTTON3 + BUTTON4);
        // Enable P1.3 interrupt
        __bis_SR_register(GIE + LPM3_bits);
        // enable interrupt and go to sleep mode until button press
        __delay_cycles(6000000);	// Time limit for pressing button
        P1IE &= ~(BUTTON1 + BUTTON2 + BUTTON3 + BUTTON4);
        // Disable P1.3 interrupt; no interrupt call when LED is not flashing
 	error_config(rec,ans,&r);	// Check if the player is right
        __delay_cycles(750000);	// wait for restart
        if ((r % 5 == 0) && (r >= 10)) {
            bonus_config();
	    __delay_cycles(800000);
	}
    }
}

void LED_config(int rand) {

	P1OUT &= ~LED1;	// LED off
	P1OUT &= ~LED2;	// LED off
	P1OUT &= ~LED3;	// LED off
	P1OUT &= ~LED4;	// LED off
	if (rand == 1) {
		P1OUT |= LED1;	// LED on
		__delay_cycles(200000);
		P1OUT &= ~LED1;	// LED off
		__delay_cycles(200000);	// Between each blink
	}
	if (rand == 2) {
		P1OUT |= LED2;	// LED on
		__delay_cycles(200000);
		P1OUT &= ~LED2;	// LED off
		__delay_cycles(200000);	// Between each blink
	}
	if (rand == 3) {
		P1OUT |= LED3;	// LED on
		__delay_cycles(200000);
		P1OUT &= ~LED3;	// LED off
		__delay_cycles(200000);	// Between each blink
	}
	if (rand == 4) {
		P1OUT |= LED4;	// LED on
		__delay_cycles(200000);
		P1OUT &= ~LED4;	// LED off
		__delay_cycles(200000);	// Between each blink
	}
}

void error_config(int record[], int answer[], int *rep) {

	int c, k;
	for (c = 0; c <= *rep; c++){
	    if (record[c] != answer[c]){ // Lose state
		for (k = 0; k < 20; k ++) {
		    P1OUT |= (LED1 + LED2 + LED3 + LED4);
		    __delay_cycles(50000);
		    P1OUT &= ~(LED1 + LED2 + LED3 + LED4);	// Blinks LED 20Hz
		    __delay_cycles(50000);
                }
		if ((*rep <= 20) || (*rep >= 49)){
		    *rep = -1;
		}   // If made some mistakes at certain games, restart as penalty
		break;	// once signal displayed, continue next round
	    }
	}
}

int shift_lfsr(unsigned int *lfsr, unsigned int polynomial_mask){

	int feedback;
	feedback = *lfsr & 1;
	*lfsr >>= 1;
	if (feedback == 1){
		*lfsr ^= polynomial_mask;
	}
	return *lfsr;
}

int randInt(void) {
	shift_lfsr(&a_mask, poly_mask);
	return ((shift_lfsr(&a_mask,
            poly_mask) ^ shift_lfsr(&a_gen, poly_gen)) & 0x03) + 1;
}

#pragma vector = PORT1_VECTOR
__interrupt void button(void) {
	count = count + 1;
	WDTCTL = WDT_ADLY_16;
	IFG1 &= ~WDTIFG;	// clear the watchdog timer interrupt flag
	IE1 |= WDTIE;
        // enable watchdog timer interrupts; in 44ms the button will be re-enabled
	if (P1IFG & BUTTON1){
		P1IE &= ~BUTTON1;	// disable interrupt for debouncing
		ans[count - 1] = 1;
		P1IFG &= ~BUTTON1;	// clear the pin interrupt flag
	}
	if (P1IFG & BUTTON2){
		P1IE &= ~BUTTON2;
		ans[count - 1] = 2;
		P1IFG &= ~BUTTON2;
	}
	if (P1IFG & BUTTON3){
		P1IE &= ~BUTTON3;
		ans[count - 1] = 3;
		P1IFG &= ~BUTTON3;
	}
	if (P1IFG & BUTTON4){
		P1IE &= ~BUTTON4;
		ans[count - 1] = 4;
		P1IFG &= ~BUTTON4;
	}
}

#pragma vector = WDT_VECTOR
__interrupt void WDT_ISR(void) {
	    IE1 &= ~WDTIE;	// Watchdog timer interrupt disable
	    IFG1 &= ~WDTIFG;	// clear interrupt flag
  	    WDTCTL = WDTPW + WDTHOLD;	// stop watchdog timer, CPU not reset
	    if (P1IFG & BUTTON1){
	    	P1IE |= BUTTON1;
	    }
	    if (P1IFG & BUTTON2){
	    	P1IE |= BUTTON2;
	    }
	    if (P1IFG & BUTTON3){
	    	P1IE |= BUTTON3;
	    }
	    if (P1IFG & BUTTON4){
	    	P1IE |= BUTTON4;
		}
	    __bic_SR_register_on_exit(LPM3_bits);
}

void bonus_config(void){
	P1OUT |= LED1;
	__delay_cycles(300000);
	P1OUT &= ~LED1;
	P1OUT |= LED2;
	__delay_cycles(300000);
	P1OUT &= ~LED2;
	P1OUT |= LED3;
	__delay_cycles(300000);
	P1OUT &= ~LED3;
	P1OUT |= LED4;
	__delay_cycles(300000);
	P1OUT &= ~LED4;
	P1OUT |= LED4;
	__delay_cycles(300000);
	P1OUT &= ~LED4;
	P1OUT |= LED3;
	__delay_cycles(300000);
	P1OUT &= ~LED3;
	P1OUT |= LED2;
	__delay_cycles(300000);
	P1OUT &= ~LED2;
	P1OUT |= LED1;
	__delay_cycles(300000);
	P1OUT &= ~LED1;
	P1OUT |= (LED1 + LED2 + LED3 + LED4);
	__delay_cycles(300000);
	P1OUT &= ~(LED1 + LED2 + LED3 + LED4);
	P1OUT |= (LED1 + LED2 + LED3 + LED4);
	__delay_cycles(300000);
	P1OUT &= ~(LED1 + LED2 + LED3 + LED4);
}