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.
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);
}