Exercise #1 :
 
  • The solution is quite simple : adding two global variables to track the current and previous state of the switch.
  • It allows testSwitch() to only change the state of the LED when the previous state is different from the current one.
#include <ESP8266WiFi.h>
#define PIN_LED D1
#define PIN_BUTTON D2
byte state;
byte oldState;

void setup() {
 
  Serial.begin(115200);
  while(!Serial); 

  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();

  pinMode(PIN_BUTTON,INPUT); // switch
  pinMode(PIN_LED,OUTPUT); // LED
  
  digitalWrite(PIN_LED,LOW); // switch off the led at start
  state = digitalRead(PIN_BUTTON);
  oldState = state;
}
 
void testSwitch() {
  state = digitalRead(PIN_BUTTON);  
  if (state != oldState) {
    if (state == 0) {
      Serial.println("switch off => light off");
      digitalWrite(PIN_LED,LOW);
    }
    else {
      Serial.println("switch on => light on");
      digitalWrite(PIN_LED,HIGH);    
    }
    oldState = state;
  }
}
 
void loop() {
  testSwitch();
  delay(10); // wait 10ms
}

 

Exercise #2 :

  • This time, an interrupt is used to check if the switch changed of state, as seen in the course. 
  • Recalls :
    • the interrupt handler function name MUST be prepend with ICACHE_RAM_ATTR
    • global variables modified by the handler must be declared as volatile
  • Using interruption implies that there is no need to remember the previous state, as in exercise 1.
#include <ESP8266WiFi.h>
#define PIN_LED D1
#define PIN_BUTTON D2
volatile byte state;

void ICACHE_RAM_ATTR testSwitch() {
  state = digitalRead(PIN_BUTTON);  
  if (state == 0) {    
    digitalWrite(PIN_LED,LOW);
  }
  else {      
    digitalWrite(PIN_LED,HIGH);    
  }    
}

void setup() {
 
  Serial.begin(115200);
  while(!Serial);
 
  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(10); 
 
  pinMode(PIN_BUTTON,INPUT); // switch
  pinMode(PIN_LED,OUTPUT); // LED
  
  digitalWrite(PIN_LED,LOW); // switch off the led at start
  state = digitalRead(PIN_BUTTON);
  attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), testSwitch, CHANGE);
}
 
void loop() {  
  delay(1000); // wait 1s
}

 

Exercise #3: 

  • As said in the subject, it is generally a good idea to implement any sketch as a state machine, provided the µC is really in different states during its execution.
  • A state machine is constituted of states, each one being associated to a given task.
  • The machine starts with a given state, thus executing the associated task.
  • When a task terminates, the state machine changes of state, or not, depending on the results of the task.
  • In literature, state machines are represented by a graph, with vertices as states/tasks, and edges as the possible changes of state.

 

  • In this exercise, there are two states, represented by an integer variable state, that is used in loop() to determine what to do :
    • if 0: the LED mus blink
    • if 1 : the user must push the button.
  • The µC must alternate between these two states, which leads to a loop() function as in the following:
loop() {
  if (state == 0) {
    ... // make the led blink
    state = 1;
  }
  else if (state == 1) {
    ... // if enough push/release:  state = 0;
  }
}
  • Note that if state = 0, there are no interactions from the user, so it is possible to do all blinks in a single call of loop().
  • In the solution below, blinks are produced by a dedicated method to shorten the loop() function. This method has parameters that fit with the need of the exercises (min/max number of blinks, gap duration, ...)
  • After that, current time is saved and state is assigned to 1.

 

  • If state = 1, it is better to use interrupts to immediately catch push button changes. It leads to a problem: to detect the end of the exercise when the user has pushed/released the button enough times. Indeed, in this case the µC must go back in state = 0.
  • The solution is to use a global boolean variable that is assigned by the interrupt handler when it detects the end of the exercise. The loop() function just "watch" this variable, checking if it is true.
  • Note that such a solution is only possible on a µC. On a classical processor, it would overload the processor.
#include <ESP8266WiFi.h>
#define PIN_LED D1
#define PIN_BUTTON D2
#define STATE_BLINK 0
#define STATE_BUTTON 1

int state;
volatile int countButton;
int nbBlinks;
long t1; // time when blink ends
volatile long t2; // time when button is pressed sufficient number of times.
volatile boolean butStop;

void ICACHE_RAM_ATTR testSwitch() {
  /* no need to know the state of the push button: testSwitch() is called
  once when pushed, and once when released. Thus, we just need to increment
  countButton each time.  If it becomes >= 2*nbBlinks, take the current time, 
  and use butStop to signal the end.  */
  countButton++;
  if (countButton >= (2*nbBlinks)) {
    t2 = millis();
    butStop = true;
  }
}

void doBlinks(int min, int max, int duration, boolean sameLast = false) {  
  nbBlinks = random(min,max);  
  int end = nbBlinks;
  if (!sameLast) end--; 
  for(int i=0;i<end;i++) {
    digitalWrite(PIN_LED, HIGH);
    delay(duration);
    digitalWrite(PIN_LED, LOW);
    delay(duration);
  }
  if (!sameLast) {
    digitalWrite(PIN_LED, HIGH);
    delay(1000);
    digitalWrite(PIN_LED, LOW);  
  }
}

void setup() {
 
  Serial.begin(115200);
  while(!Serial);
 
  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
 
  pinMode(PIN_BUTTON,INPUT); // switch
  pinMode(PIN_LED,OUTPUT); // LED
  
  digitalWrite(PIN_LED,LOW); // switch off the led at start
  state = STATE_BLINK; 
  attachInterrupt(digitalPinToInterrupt(PIN_BUTTON), testSwitch, CHANGE);
}

void loop() {  
  if (state == STATE_BLINK) {
    Serial.println("be ready ...");
    delay(5000); // wait for 5sec
    Serial.println("count the blinks");
    doBlinks(3,6, 500);  
    Serial.println("now push the button");
    t1 = millis();
    state = STATE_BUTTON;
    butStop = false;
    countButton = 0;
  }
  else {
    if (butStop) {
      digitalWrite(PIN_LED,HIGH);
      delay(200);
      digitalWrite(PIN_LED, LOW);
      Serial.println("time to react: "+String(t2-t1)+"ms");    
      state = STATE_BLINK;
    }
    else {
      delay(5);
    }
  }
}

 

Exercise #4: 

  • Even if the IR receiver data pin is connected to a single pin on the µC, it does not deliver a simple boolean signal. It uses a communication protocol to send the code of the button pressed.
  • Thus, it is not possible to use an interrupt and all is done by testing if something is received as fast as possible.
  • Note that the button code may differ using another type of IR remote controller
#include <ESP8266WiFi.h>
#include <IRremote.hpp>

#define IR_RECEIVE_PIN D3

#define STATE_BLINK 0
#define STATE_BUTTON 1

#define BUT0 0XE619FF00
#define BUT1 0xBA45FF00
#define BUT2 0XB946FF00
#define BUT3 0XB847FF00
#define BUT4 0XBB44FF00
#define BUT5 0XBF40FF00
#define BUT6 0XBC43FF00
#define BUT7 0XF807FF00
#define BUT8 0XEA15FF00
#define BUT9 0XF609FF00


int state;
int nbBlinks;
long t1; // time when blink ends
long t2; // time when button is pressed sufficient number of times.

void doBlinks(int min, int max, int duration, boolean sameLast = false) {  
  nbBlinks = random(min,max);  
  int end = nbBlinks;
  if (!sameLast) end--; 
  for(int i=0;i<end;i++) {
    digitalWrite(D1, HIGH);
    delay(duration);
    digitalWrite(D1, LOW);
    delay(duration);
  }
  if (!sameLast) {
    digitalWrite(D1, HIGH);
    delay(1000);
    digitalWrite(D1, LOW);  
  }
}

boolean checkCode(int rawCode) {
  int code = 0;
  if (rawCode == BUT0) {
    code = 0;    
  }
  switch (rawCode) {
    case BUT0: code = 0; break;
    case BUT1: code = 1; break;
    case BUT2: code = 2; break;
    case BUT3: code = 3; break;
    case BUT4: code = 4; break;
    case BUT5: code = 5; break;
    case BUT6: code = 6; break;
    case BUT7: code = 7; break;
    case BUT8: code = 8; break;
    case BUT9: code = 9; break;
  }
  if(code == nbBlinks) return true;
  return false;
}

void setup() {
 
  Serial.begin(115200);
  delay(10);
 
  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(10); 
 
  pinMode(D2,INPUT); // switch
  pinMode(D1,OUTPUT); // LED
  digitalWrite(D1,LOW); // switch off the led at start
  delay(10);  

  state = STATE_BLINK; 
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // Start the receiver
  delay(10);
}
 
void loop() {  
  if (state == STATE_BLINK) {
    Serial.println("be ready ...");
    delay(5000); // wait for 5sec
    Serial.println("count the blinks");
    doBlinks(2, 10, 500);  
    t1 = millis();
    Serial.println("now choose the good number");
    state = STATE_BUTTON;  
  }
  else {
    if (IrReceiver.decode()) {      
      if (checkCode(IrReceiver.decodedIRData.decodedRawData)) {
        t2 = millis();
        digitalWrite(D1,HIGH);
        delay(200);
        digitalWrite(D1, LOW);
        Serial.println("reaction time: "+String(t2-t1)+"ms");
        state = STATE_BLINK;          
      }
      IrReceiver.resume(); // Enable receiving of the next value
    }
  }
}
 

 

Exercise #5: 

  • As for the IR receiver, it is not possible to detect a change on an analog pin with an interrupt.
  • Thus, the solution is quite similar to the exercise 4: reading the value on the analog pin and testing if it is > 200. Small difference: we cannot do this asa fast as possible and must include a small delay of 3ms between readings. Otherwise, the esp8266 would crash.
#include <ESP8266WiFi.h>

#define STATE_BLINK 0
#define STATE_BUTTON 1

int state;
int countHit;
int nbBlinks;
long t1; // time when blink ends
long t2; // time when button is pressed sufficient number of times.

void doBlinks(int min, int max, int duration, boolean sameLast = false) {  
  nbBlinks = random(min,max);  
  int end = nbBlinks;
  if (!sameLast) end--; 
  for(int i=0;i<end;i++) {
    digitalWrite(D1, HIGH);
    delay(duration);
    digitalWrite(D1, LOW);
    delay(duration);
  }
  if (!sameLast) {
    digitalWrite(D1, HIGH);
    delay(1000);
    digitalWrite(D1, LOW);  
  }
}

void setup() {
 
  Serial.begin(115200);
  delay(10);
 
  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(10); 
 
  pinMode(D2,INPUT); // switch
  pinMode(D1,OUTPUT); // LED
  digitalWrite(D1,LOW); // switch off the led at start
  delay(10);  

  state = STATE_BLINK; 
  delay(10);
}
 
 
void loop() {  
  if (state == STATE_BLINK) {
    Serial.println("be ready ...");
    delay(5000); // wait for 5sec
    Serial.println("count the blinks");
    doBlinks(3,7,500);  
    t1 = millis();
    Serial.println("now hit the piezo");
    state = STATE_BUTTON;
    countHit = 0;
  }
  else {
    int l = analogRead(A0);
    if (l >= 200) {
      countHit++;
      if (countHit >= nbBlinks) {
        t2 = millis();
        digitalWrite(D1,HIGH);
        delay(200);
        digitalWrite(D1, LOW);
        Serial.println("reaction time: "+String(t2-t1)+"ms");
        state = STATE_BLINK;                
      }
      delay(50); // wait for piezo to RAZ
    }
    else {
      delay(3);
    }
  }
}
 

Exercise #6: 

  • As said in the subject, the solution uses 5 states, to differentiate the different game modes.
  •  Apart that, there are some extra variables that allow to test if button or piezo have been pressed/hit sufficient number of times, and few instructions to manage the fact that there are two buttons to press on the IR when there are more than 9 blinks.

 

#include <ESP8266WiFi.h>
#include <IRremote.hpp>

#define IR_RECEIVE_PIN D3

#define STATE_BLINK 0
#define STATE_BUTTON 1
#define STATE_PIEZO 2
#define STATE_IR 3
#define STATE_END 4

#define MODE_BUT_PIEZO 1
#define MODE_PIEZO_BUT 2
#define MODE_IR 3

#define BUT0 0XE619FF00
#define BUT1 0xBA45FF00
#define BUT2 0XB946FF00
#define BUT3 0XB847FF00
#define BUT4 0XBB44FF00
#define BUT5 0XBF40FF00
#define BUT6 0XBC43FF00
#define BUT7 0XF807FF00
#define BUT8 0XEA15FF00
#define BUT9 0XF609FF00


int state;
int nbBlinks1;
int nbBlinks2;
int gameMode;
long t1; // time when blink ends
long t2; // time when button is pressed sufficient number of times.
volatile int countButton;
volatile boolean butReleased; // set to true if pressed then released
volatile boolean butStop; // used to signal count for but is reached
int countHit;
boolean hitStop; // used to signal count for piezo is reached
int countIR; // number of buttons on IR contrller to press (depends on nbBlinks1+nbBlinks2)
int digits[2];

void ICACHE_RAM_ATTR testSwitch() {  
  // only do something when state = STATE_BUTTON
  if (state != STATE_BUTTON) return;
  // just increment and test if released. All checks are done in loop()
  countButton++;
  if (countButton%2 == 0) butReleased = true;    
}

int doBlinks(int min, int max, int duration, boolean sameLast = false) {  
  int nbBlinks = random(min,max+1);  
 
  int end = nbBlinks;
  if (!sameLast) end--; 
  for(int i=0;i<end;i++) {
    digitalWrite(D1, HIGH);
    delay(duration);
    digitalWrite(D1, LOW);
    delay(duration);
  }
  if (!sameLast) {
    digitalWrite(D1, HIGH);
    delay(1000);
    digitalWrite(D1, LOW);  
  }
  return nbBlinks;
}

boolean checkCode(int rawCode, int value) {
  int code = 0;
  if (rawCode == BUT0) {
    code = 0;    
  }
  switch (rawCode) {
    case BUT0: code = 0; break;
    case BUT1: code = 1; break;
    case BUT2: code = 2; break;
    case BUT3: code = 3; break;
    case BUT4: code = 4; break;
    case BUT5: code = 5; break;
    case BUT6: code = 6; break;
    case BUT7: code = 7; break;
    case BUT8: code = 8; break;
    case BUT9: code = 9; break;
  }
  if(code == value) return true;
  return false;
}

void setup() {
 
  Serial.begin(115200);
  delay(10);
 
  // power off Wifi to save energy
  WiFi.mode(WIFI_OFF);
  WiFi.forceSleepBegin();
  delay(10); 
 
  pinMode(D2,INPUT); // switch
  pinMode(D1,OUTPUT); // LED
  digitalWrite(D1,LOW); // switch off the led at start
  delay(10);  

  state = STATE_BLINK; 
  IrReceiver.begin(IR_RECEIVE_PIN, ENABLE_LED_FEEDBACK); // Start the receiver
  delay(10);
}
 
void loop() {  
  if (state == STATE_BLINK) {
    Serial.println("be ready ...");
    delay(5000); // wait for 5sec
    Serial.println("count the blinks");
    nbBlinks1 = doBlinks(3,6,300,true);
    delay(1000);
    nbBlinks2 = doBlinks(3,6,300,true);    
    delay(1000);
    gameMode = doBlinks(1,3,500); // blink for game mode
  
    //Serial.println("game mode: "+String(gameMode));
    countButton = 0;
    countHit = 0;
    hitStop = false;
    butStop = false;
    butReleased = false;
    
    if (gameMode == MODE_BUT_PIEZO) {
      state = STATE_BUTTON;  
      attachInterrupt(digitalPinToInterrupt(D2), testSwitch, CHANGE);
    }
    else if (gameMode == MODE_PIEZO_BUT) {
      state = STATE_PIEZO;
      attachInterrupt(digitalPinToInterrupt(D2), testSwitch, CHANGE);
    }
    if (gameMode == MODE_IR) {
      int sum = nbBlinks1+nbBlinks2;      
      if (sum >= 10) {
        countIR = 1;
        digits[1] = sum/10;
        digits[0] = sum%10;
      }
      else {
        countIR = 0;
        digits[0] = sum;        
      }
      state = STATE_IR;
    }
    t1 = millis();
  }
  else if (state == STATE_IR) {
      
      if (IrReceiver.decode()) {
        /* check if IR code = the current digit.
           E.g. if must enter 12, then countIR is initialized to 1 and digits[1] = 1, digits[0] = 2
        */
        if (checkCode(IrReceiver.decodedIRData.decodedRawData, digits[countIR])) {
          if (countIR == 0) {
            t2 = millis();            
            state = STATE_END;          
          }
          else {
            countIR--;
          }
        }
        IrReceiver.resume(); // Enable receiving of the next value
      }    
  }
  else if (state == STATE_BUTTON) {
    if (butReleased) {   
      //Serial.println("but released");
      // whatever the mode, check if button count is reached.
      if ( ((gameMode == MODE_BUT_PIEZO) && (countButton >= (2*nbBlinks1))) || 
            ((gameMode == MODE_PIEZO_BUT) && (countButton >= (2*nbBlinks2))) ) {     
          butStop = true;          
      }
      butReleased = false;
      // test end of game      
      if (butStop && hitStop) {
        t2 = millis();          
        state = STATE_END;
      }
      // else wait for piezo only if not already ended
      else if (!hitStop) {            
        state = STATE_PIEZO;                          
      }
      // else continue with button
    }
  }
  else if (state == STATE_PIEZO) {
    int l = analogRead(A0);
    if (l >= 200) {
      //Serial.println("hit");
      countHit++;
      // whatever the mode, check if hit count is reached.
      if ( ((gameMode == MODE_BUT_PIEZO) && (countHit >= nbBlinks2)) || 
            ((gameMode == MODE_PIEZO_BUT) && (countHit >= nbBlinks1)) ) {     
          hitStop = true;          
      }
      if (butStop && hitStop) {
        t2 = millis();          
        state = STATE_END;
      }
      // else wait for button only if not already ended
      else if (!butStop) {            
        state = STATE_BUTTON;                          
      }
      // else continue with piezo
      else {
        delay(50); // wait for piezo to RAZ
      }
    }
  }
  else if (state == STATE_END) {
    digitalWrite(D1,HIGH);
    delay(200);
    digitalWrite(D1, LOW);
    Serial.println("reaction time: "+String(t2-t1)+"ms");
    state = STATE_BLINK;   
  } 
}