11. Input Devices

This week we worked with input devices. These are devices that provide information about our environment which can then be used by a microcontroller we program to make decisions. An example of an input device is a button which sends a HIGH or LOW to the microcontroller or a temperature sensor which gives an analog input, basically a voltage between ground and the maximum (for most microcontrollers 3.3 volts but sometimes 5).

Since I've already done this assignment incidentally in a previous week I've decided to use that documentation here.

Buttons

To fulfill this week's task I made a simple program to display the button pressed (input) on the TFT screen (output) of my microcontroller. I repurposed some of my game code for ther assignment. The relevant code for this week is inputs so that is what I will focus on.

First I set the pins for the button inputs.


        void pinSetup(){
          pinMode(UP_BTN, INPUT_PULLUP);
          pinMode(DOWN_BTN, INPUT_PULLUP);
          pinMode(LEFT_BTN, INPUT_PULLUP);
          pinMode(RIGHT_BTN, INPUT_PULLUP);
          pinMode(A_BTN, INPUT_PULLUP);
          pinMode(B_BTN, INPUT_PULLUP);
        }
      

Then another function sets the state of each of these buttons (whether or not they were pressed)


        void inputs(){
          if (digitalRead(A_BTN) == LOW) {
            controller.setABtn(ButtonState::pressed);
          } else {
            controller.setABtn(ButtonState::released);
          }
          if (digitalRead(B_BTN) == LOW) {
            controller.setBBtn(ButtonState::pressed);
          } else {
            controller.setBBtn(ButtonState::released);
          }
          if (digitalRead(DOWN_BTN) == LOW) {
            controller.setDownBtn(ButtonState::pressed);
          } else {
            controller.setDownBtn(ButtonState::released);
          }
          if (digitalRead(UP_BTN) == LOW) {
            controller.setUpBtn(ButtonState::pressed);
          } else {
            controller.setUpBtn(ButtonState::released);
          }
          if (digitalRead(LEFT_BTN) == LOW) {
            controller.setLeftBtn(ButtonState::pressed);
          } else {
            controller.setLeftBtn(ButtonState::released);
          }
          if (digitalRead(RIGHT_BTN) == LOW) {
            controller.setRightBtn(ButtonState::pressed);
          } else {
            controller.setRightBtn(ButtonState::released);
          }

        }
      

Then in the loop these states are checked. If a button is pressed then the button name is displayed on the screen.


        void loop() {
          inputs();
          if (controller.up == ButtonState::pressed){
            Serial.println("Up Pressed");
            repaint("Up");
          } else if (controller.down == ButtonState::pressed) {
            Serial.println("Down Pressed");
            repaint("Down");
          }else if (controller.left == ButtonState::pressed) {
            Serial.println("Left Pressed");
            Srepaint("Left");
          }else if (controller.right == ButtonState::pressed) {
            Serial.println("Right Pressed");
            repaint("Right");
          }
        }
      

This was the result

Displaying input. But flickery

Well the inputs seemed to be working well but there seemed to be an issue. There was a flicker on the screen each time a button was pressed. This indicates the button is "bouncing". The best way to deal with this is wait a certain amount of time and if the state is still the same (ie the same button is being pressed) we can say with more certainty that it is actually pressed or not.


        int prevState = -1;
        int timeSince = millis();
        bool currDebounced = false;
        bool debounce(int currState) {
          bool debounced = false;
          if (prevState != currState) {
            timeSince = millis();
            currDebounced = false;
          } else if (millis() - timeSince > 50) {
            debounced = !currDebounced;
            currDebounced = true;
          }
          prevState = currState;
          if (debounced){
            Serial.println("pressed");
          }
          return debounced;
        }
      

I added a debounce function that takes a current state which is passed when a button is pressed here in the loop function


        void loop() {
          inputs();
          if (controller.up == ButtonState::pressed && debounce(UP_BTN)){
            repaint("Up");
          } else if (controller.down == ButtonState::pressed && debounce(DOWN_BTN)) {
            repaint("Down");
          }else if (controller.left == ButtonState::pressed && debounce(LEFT_BTN)) {
            repaint("Left");
          }else if (controller.right == ButtonState::pressed && debounce(RIGHT_BTN)) {
            repaint("Right");
          }
        }
      

It then checks how long since the last state change. If it is over 100 milliseconds we can say the state is "verified" and can repaint the screen. We also then set another global state variable, currDebounced, to ensure we don't repaint the screen many times over. Here is the result

Debounced inputs

Code files

Arduino code can be found here.