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