Lab 2 — Sensors and Input Devices

Lab 2 — Sensors and Input Devices

Welcome to Lab 2! In this lab we’ll explore how to use sensors! Before doing the lab, please take a look at Reading 3 which provides an introduction to sensors!

ℹ️
This second lab focuses on giving you a quick introduction on how to interface sensors with the Arduino. This lab will be expanded in the future to include things such as filtration, sampling, interrupts, calibration, and more.
🚫
For the first time in this lab we’ll be wiring up components. Remember back to Reading 1 where we talked about polarity, voltage, and current ratings. Make sure you’re wiring your sensors correctly otherwise they could break!

Note that this lab will also assume you are comfortable with the concepts presented in Lab 1. That include creating new variables in the online Arduino IDE. Refer back to Lab 1 if you ever need a refresher! But try to do it from memory 🙂! First, it might be worth skimming the Connecting Sensors Arduino tutorial as we will be connecting an analog, digital, and I2C sensor to our arduino today as well.

Connecting the Arduino Shield

First, grab your Arduino and the MKR Connector Carrier. The connector is simply a shield for the Arduino. Shields take all the little Arduino pins and connect them to other hardware. In this case, this shield simply connects the Arduino to all the little white connectors that will let us plug in sensors. It’s all pre-wired for us! Isn’t that great!

Place the Arduino on the shield carefully, make sure the USB port faces away from the blue screw terminal connectors like shown in the image below.

Reading from a Button (Digital Sensor)

We’re going to start with the easiest sensor, a button. The button will give the Arduino a “high” or “low” signal. Grab one of the LED buttons from your sensor kit and wire it to the D5/D6 port. Note that the sensor is plugged into D0 in the image.

Now note that the Arduino Cloud plan likely won’t let us create a new thing or program because we’ve reached our plan limit of two things (boo subscription software, bring my floppy disks back). No matter, just click on your LED program in Arduino cloud so we can cheat the system. Delete both the variables you currently have in the LED program, and then delete any code associated with them. Your code file and dashboard should look like the below.

/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/d40137c5-9c2a-465c-9779-65d8ff950cb2 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  - No variables have been created, add cloud variables on the Thing Setup page
    to see them declared here

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
// add the import
#include <utility/wifi_drv.h>

void setup() {
  //add the LED pins
  WiFiDrv::pinMode(25, OUTPUT); //define green pin
  WiFiDrv::pinMode(26, OUTPUT); //define red pin
  WiFiDrv::pinMode(27, OUTPUT); //define blue pin
  
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  //delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here 
  
}


// WiFiDrv::analogWrite(25, 255);
// WiFiDrv::analogWrite(26, 0);
// WiFiDrv::analogWrite(27, 0);

// WiFiDrv::analogWrite(25, 0);
// WiFiDrv::analogWrite(26, 255);
// WiFiDrv::analogWrite(27, 0);

Now in the code file we want to start by defining some pins. First we should define the pin for the button, and the pin for the LED that’s in the button. Under the imports add the following lines of code.

//pins
const int ledPin = 5;      // the number of the LED pin, D3
const int buttonPin = 6;

Now let’s add some variables to track what’s going on with the button and the LED. Add these under the imports as well.

// Variables will change:
int ledState = LOW;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH; 

Next we need to tell the Arduino which pins we are using and what we’re using them for. Since we plugged the button and LED into pins 5 and 6, we need to tell the Arduino that there’s an LED plugged into pin 5 (a digital output), and a button plugged into pin 6 (a digital input). In the setup() loop add the following code.

//setup pins
pinMode(buttonPin, INPUT);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, ledState);

Now we’re ready to read from the digital pin in the main loop. Every time the button is pressed we will toggle a boolean variable between true and false. So push once, turns on, push again, turns off, and we’ll use the LED on the button to track if that value is on or off. Add the following code to the main loop.

// LED CODE
int reading = digitalRead(buttonPin);

// if the button state has changed:
if (reading != buttonState) {
  buttonState = reading;
  
  if (buttonState == LOW) {  //button is pressed
    ledState = HIGH;
  }
  else {
    ledState = LOW;
  }
}

digitalWrite(ledPin, ledState);

The code should look like this so far.

/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/d40137c5-9c2a-465c-9779-65d8ff950cb2 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  - No variables have been created, add cloud variables on the Thing Setup page
    to see them declared here

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
// add the import
#include <utility/wifi_drv.h>

//pins
const int ledPin = 5;      // the number of the LED pin, D5
const int buttonPin = 6;

// Variables will change:
int ledState = LOW;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH; 

void setup() {
  //add the LED pins
  WiFiDrv::pinMode(25, OUTPUT); //define green pin
  WiFiDrv::pinMode(26, OUTPUT); //define red pin
  WiFiDrv::pinMode(27, OUTPUT); //define blue pin
  
  //setup pins
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);

  
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  //delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here 
  
  // LED CODE
  int reading = digitalRead(buttonPin);
  
  // if the button state has changed:
  if (reading != buttonState) {
    buttonState = reading;
    
    if (buttonState == LOW) {  //button is pressed
      ledState = LOW;
    }
    else {
      ledState = HIGH;
    }
  }
  
  digitalWrite(ledPin, ledState);
}


// WiFiDrv::analogWrite(25, 255);
// WiFiDrv::analogWrite(26, 0);
// WiFiDrv::analogWrite(27, 0);

// WiFiDrv::analogWrite(25, 0);
// WiFiDrv::analogWrite(26, 255);
// WiFiDrv::analogWrite(27, 0);

Now go ahead and upload this! And watch it not work 😛!

Debounce Filtration

Ok so why didn’t that work? Well it has to do with a concept called debounce. A good explination of debounce is contained in the video below.

Essentially what is happening is the mechanical switch is not perfect. So we’re getting a bunch of on and off signals when we press the button. The arduino operates really fast so it can read all of them, and it gets confused. We need to implement what’s called a debounce filter.

At the very top of the code add the following lines under the imports. These are variables that will store information needed for the filter.

// debounce variables
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50; 

Then after the digitalRead(buttonPin) insert the following piece of code. Basically we restart at timer everytime we see the button change.

// If the switch changed, due to noise or pressing:
if (reading != lastButtonState) {
  // reset the debouncing timer
  lastDebounceTime = millis();
}

Now, we want to only change the button state if we’ve seen the same state for more than 50ms. Wrap the button code in the main loop in a if-then statement.

if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer than the debounce
    // delay, so take it as the actual current state:

    // -- BUTTON CODE --

}

Finally, reset the last button state variable.

lastButtonState = reading;

Your final code should look like this.

/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/d40137c5-9c2a-465c-9779-65d8ff950cb2 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  - No variables have been created, add cloud variables on the Thing Setup page
    to see them declared here

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
// add the import
#include <utility/wifi_drv.h>

//pins
const int ledPin = 5;      // the number of the LED pin, D5
const int buttonPin = 6;

// Variables will change:
int ledState = LOW;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH; 

// debounce variables
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50; 

void setup() {
  //add the LED pins
  WiFiDrv::pinMode(25, OUTPUT); //define green pin
  WiFiDrv::pinMode(26, OUTPUT); //define red pin
  WiFiDrv::pinMode(27, OUTPUT); //define blue pin
  
  //setup pins
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);

  
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  //delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here 
  
  // LED CODE
  int reading = digitalRead(buttonPin);
  
  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }
  
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {  //button is pressed
          ledState = !ledState;
      }

    }
  }
  
  digitalWrite(ledPin, ledState);
  lastButtonState = reading;
}


// WiFiDrv::analogWrite(25, 255);
// WiFiDrv::analogWrite(26, 0);
// WiFiDrv::analogWrite(27, 0);

// WiFiDrv::analogWrite(25, 0);
// WiFiDrv::analogWrite(26, 255);
// WiFiDrv::analogWrite(27, 0);

Now the LED should turn on and off as you press the button. Now let’s connect this to the IoT cloud!

Connecting Sensors to the IoT Cloud

Add a variable called “button” to your thing’s dashboard. Make it a boolean variable that updates every 1 second automatically.

Now updating it is easy! Just set the following line of code in the main loop.

button = ledState;

Now if the LED is on, the cloud variable will also be on! The final code looks like this.

/* 
  Sketch generated by the Arduino IoT Cloud Thing "Untitled"
  https://create.arduino.cc/cloud/things/d40137c5-9c2a-465c-9779-65d8ff950cb2 

  Arduino IoT Cloud Variables description

  The following variables are automatically generated and updated when changes are made to the Thing

  bool button;

  Variables which are marked as READ/WRITE in the Cloud Thing will also have functions
  which are called when their values are changed from the Dashboard.
  These functions are generated with the Thing and added at the end of this sketch.
*/

#include "thingProperties.h"
// add the import
#include <utility/wifi_drv.h>

//pins
const int ledPin = 5;      // the number of the LED pin, D5
const int buttonPin = 6;

// Variables will change:
int ledState = LOW;         // the current state of the output pin
int buttonState;             // the current reading from the input pin
int lastButtonState = HIGH; 

// debounce variables
unsigned long lastDebounceTime = 0;  // the last time the output pin was toggled
unsigned long debounceDelay = 50; 

void setup() {
  //add the LED pins
  WiFiDrv::pinMode(25, OUTPUT); //define green pin
  WiFiDrv::pinMode(26, OUTPUT); //define red pin
  WiFiDrv::pinMode(27, OUTPUT); //define blue pin
  
  //setup pins
  pinMode(buttonPin, INPUT);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);

  
  // Initialize serial and wait for port to open:
  Serial.begin(9600);
  // This delay gives the chance to wait for a Serial Monitor without blocking if none is found
  //delay(1500); 

  // Defined in thingProperties.h
  initProperties();

  // Connect to Arduino IoT Cloud
  ArduinoCloud.begin(ArduinoIoTPreferredConnection);
  
  /*
     The following function allows you to obtain more information
     related to the state of network and IoT Cloud connection and errors
     the higher number the more granular information you’ll get.
     The default is 0 (only errors).
     Maximum is 4
 */
  setDebugMessageLevel(2);
  ArduinoCloud.printDebugInfo();
}

void loop() {
  ArduinoCloud.update();
  // Your code here 
  
  // LED CODE
  int reading = digitalRead(buttonPin);
  
  // If the switch changed, due to noise or pressing:
  if (reading != lastButtonState) {
    // reset the debouncing timer
    lastDebounceTime = millis();
  }
  
  if ((millis() - lastDebounceTime) > debounceDelay) {
    // whatever the reading is at, it's been there for longer
    // than the debounce delay, so take it as the actual current state:

    // if the button state has changed:
    if (reading != buttonState) {
      buttonState = reading;

      if (buttonState == LOW) {  //button is pressed
          ledState = !ledState;
      }

    }
  }
  
  digitalWrite(ledPin, ledState);
  lastButtonState = reading;
  button = ledState;
}


// WiFiDrv::analogWrite(25, 255);
// WiFiDrv::analogWrite(26, 0);
// WiFiDrv::analogWrite(27, 0);

// WiFiDrv::analogWrite(25, 0);
// WiFiDrv::analogWrite(26, 255);
// WiFiDrv::analogWrite(27, 0);



/*
  Since Button is READ_WRITE variable, onButtonChange() is
  executed every time a new value is received from IoT Cloud.
*/
void onButtonChange()  {
  // Add your code here to act upon Button change
}

Go to your dashboards and make a graph that graphs the button value! Play with the button and make sure the graph updates correctly!

🚫
Call a staff member over for a checkoff. Explain what the limitation in this system is. Hint: this is your first introduction to the concepts of update time and latency.

Reading from a Potentiometer (analog sensor)

Take the two-axis joystick and connect it to the A5/A6 port on the shield board.

Inside the joystick are two potentiometers. A potentiometer is a variable resistor. A basic video on how a potentiometer works is linked below.

The potentiometers are powered by 3.3V from the arduino, and they create voltage dividers whos signal is outputted to the A5 and A6 pins as we have wired it. As we move the joystick around, the voltage on the A5 and A6 pins change between 0-3.3V depending on the position of the joystick. And we will read this voltage from the Arduino.

How Analog Read Works on the Arduino

The Arduino MKR has a 12 bit ADC. Which means it takes the analog signal from 0-3.3V and stores it in a 12-bit number (this is an integer number between 0-4095). The resolution at which it can read the analog signal is $3.3V / 4096 = 0.00080566406V$, this means it can read the signal in 0.0008V steps.

So for example, if the analog signal going into the Arduino is at 3.3V, the analogRead() function will output 4096. If the signal is at 1.5V, the analogRead() function will output $1.5V/3.3V * 4096 = 1861$!

Modifying the Code on the Arduino

Turns out modifying the Arduino code for reading from an analog sensor is simpler than reading from a digital sensor! First, we need to define the pins we will be reading from. Put these right under the imports.

#define xPin A6;      
#define yPin A5;

Next we want to create two variables to store the values of the analog signal that we’re going to read. Put these right under the imports as well.

// analog read
int x;
int y;

Now to read from the sensor, we just use the analogRead() function in the main loop as follows.

x = analogRead(xPin);
y = analogRead(yPin);

Finally, we want to add two variables to the setup section that update every 1s, and update the values with the values of x and y! Make these floating point numbers.

Then add the following code to the main loop. Here we convert x and y back to their voltages, and then update the values of the cloud variables.

x_sync = x/4096.0*3.3;
y_sync = y/4096.0*3.3;

Once again, try to update a dashboard so you can see the values of x and y on a graph!

🚫
Call a staff member over for a checkoff.

Adding Libraries for Serial-Based Sensors

Some sensor use digital communication to send some numbers instead of just an “on” or “off” signal. With sensors like these, we generally need to use libraries that contain the code required to “talk” to the sensor. Here’s how to install a library in the online Arduino IDE.

Go to the Seed Studio Temperature and Humidity Sensor Library Releases Page. Download the Source Code (zip) file.

Next, go to the sketch that we’ve been typing in, and hit “open in full editor.”

Once you’re in the full editor, go to the libraries tab, and then custom libraries. Hit “import” and drag the zip file you just downloaded into the window. It should upload sucessfully and say something like “grove temperature and humidity sensor library.”

Required Challenge: Get 1-2 More Sensors Working

Take a look through your kit. Choose 1-2 other sensors that require libraries to connect to your arduino and get working!

We don’t want to tell you how to use these sensors because it’s an important skill to be able to read and understand documentation. Even if it’s bad! But we are here to help if needed!

List of Sensors

We’ve taught you the basics of reading from sensors. For your projects you’ll get to choose the sensors you want to use! Here’s a list of sensors just to get those brain juices flowing!

Environmental / Chemical / Water Quality

  • Ph Sensor
  • CO2 Gas
  • Electrical Conductivity
  • Formaldehyde Sensor
  • Capacitive Soil Moisture Sensor
  • Oxygen Sensor
  • Ozone Sensor
  • Air Quality Sensor
  • Barometer
  • Dust Sensor
  • Humidity Sensor (Air)
  • Humidity Sensor (Soil)
  • Oxidation Reduction Potential Sensors

General

  • Liquid Level Sensor
  • Accelerometer (3-6 Axis)
  • IR Proximity Sensors
  • Digital IR Temperature Sensor
  • Hall (magnetic field) Sensor
  • IR Emitter/Reciever Breakbeam
  • Reflectivity Sensor
  • Light Sensor
  • Line Finder
  • PIR Motion Sensor
  • Sound Sensor
  • Touch Sensor / Button
  • Ultrasonic Distance Sensors

Energy Sensors

  • Current Sensor
  • Voltage Sensor

Specialized Sensors Robotics

  • Guesture Sensor
  • AI Machine Vision Sensor
  • Microwave Motion Radar
  • NFC/RFID Reader

Switches and Joysticks

  • Thumb Joystick
  • Potentiometer
  • Magnetic Switch
  • Momentary Switch
  • Pushbutton Switch
  • Latching Switch

Specialzied Gas Sensors

  • Alcohol Breathalyzer Sensor
  • Combustible Gas Sensor (MQ2/MQ3/MQ9)

Specialized Interfaces (Shields)

  • Display / Touchscreen
  • GPS Shields
  • CAN Bus
  • Ethernet
  • Inertial Measurement Unit (IMU)
  • SD Card
  • GSM/Cellular
  • DAQ/ADC
  • Signal Amplifiers
  • etc.

Places to Look for Sensors

Lab Planning

Essentially THIS Connecting Sensors but with more info.

Lab Equipment Needed

Analog Sensor: Button Sensor: I2C Sensor:

Lab Outline

  • Types of GPIOs
    • analog
    • INPUT PULLUP and PULLDOWN for digital input
  • At least (1x) analog sensor
    • manually reading an analog sensor with a multimeter
    • ADC discretization ( datatypes from AnalogRead() )
  • At least (1x) button, an RC de-bounce filter (this IS a digital sensor)
    • this is the debounce circuit - show WITH and WITHOUT
  • At least (1x) digital sensor, like an ENCODER, or I2C, or SPI sensor
    • Moving Average Filters (smoothing show WITH and WITHOUT)
  • Samping and the Nyquist Frequency
    • How fast should you read
    • How fast can you read
    • (this is DIFFERENT for Analog and Digital)
  • Techniques
    • Using an Analog Sensor with a Digital Input (automated thresholding) ???
    • Hardware filtering for analog sensors
    • triggering interrupts with digital sensors
    • triggering interrupts with timers

Lab 2 (sensors and input devices)

  • analog vs digital input
  • ADCs and the concept of discretiation
  • nyquist and sampling frequency
  • familiarity with various sensors
  • basic moving average filters
  • hardware filters such as RC filters and debounce do a debounce filter!!!
    • we will also use INTERRTUPTS here!!!
  • I2C, serial, and digital communications sensors
  • calculate communications speed for data streamed to the computer or the cloud

Also learn to…

  • REMBER BACK TO READING 1 and RATINGS
  • CHECK POLARITY AND CONTINUITY
  • Read from a sensor and print to serial terminal (printing variables as well)
  • Then print to serial plotter

Calibration and TRUSTING your sensors!!!! –> how to know when you trust your sensor!