Sensor Data on Interval/Demand
A simple sensor-capturing device is often useful especially during early discovery stages of a design project. Developing an ability to easily log sensor data points means we can focus on making sense of the data, rather than getting lost in the technical prototyping processes. In order to understand how to set up a sensor data collection device, we need to establish an anticipation as to how we intend to process the data.
When it comes to retrieving sensor data, there are two main ways to approach this: time-based, or cross-sectional.
Most, if not all, of the Sensing Recipes contain sample code that demonstrate interval-based readouts, i.e. time series data generation. There might be times (pun not intended) where manual triggers of sensor measurements are helpful.
Time’s arrow… time series vs cross-sectional data
Outside of Einstein’s Theory of Relativity, time tends to be perceived in everyday human experience as immutable; a constantly presence and incrementing dimension through which we can observe life changing around us. Therefore, it is not surprising that data collection often requires a time element to keep track of the data’s trajectory.
Data analysis by its nature requires datasets to be ordered around one or more axes. Given this unwavering progression of time, we are most familiar with time series charts, where the time sits on the X-axis on a plotted graph:
There is another way in which we can capture data, what we call cross-sectional data. What you choose is determined by your data analysis scenario:
1. Time series data…
places temporal changes at the core of the analysis, where multiple readings are taken over time. An example of this will be the measurement of UV light falling on a north-facing window across the entire year. A data plot of this might thus reveal patterns of UV light radiation at the site.
Further in, we have regular and irregular time series data.
Regular time series
Data has equidistant intervals between each data point i.e. every x seconds, hours, or months. In the above example, it would make sense to record a regular time series data of UV light radiation at the site, since the objective is to analyze the annual exposure patterns of UV light radiation.
Our sensing recipes adopt this interval-based approach.
Irregular time series
Data does not have equidistant intervals between data points. Instead, it records data when data is available, which eliminates the possibility of a long period of non-significant (i.e. inconsequential) data. An example of this might be the years in which trees are planted in a local council, or the logging of earthquake events and its intensity. Having a regular time series in this case will only generate a huge volume of inconsequential data in between events.
Regular/irregular time series isn’t necessarily a didactic, though. For example, if the sensor was somehow interrupted during a reading, it might ‘miss’ those reported intervals. This happens a lot in wireless sensing networks, and the design of the system that processes the data must take this into consideration.
2. Cross-sectional data…
‘ignores’ time as the primary axis, but instead places emphasis on other factors measured in that one instance of time – a ‘snapshot’, if you will, of the sensed phenomena. As the name implies, the study is of the inter-relation of the captured set of data, of which time is non-significant. An example of this will be the use of a UV light sensor, a temperature/humidity sensor, and an ambient light sensor to correlate whether the presence of UV light is independent of heat/humidity and observable visible light. In this sense, time isn’t discarded, but the relationship between other phenomena is prioritised over the observation of changes over time.
Again, which type of data collection will you use depends on what and why you are collecting the quantitative data for.
With that said, let’s see how we can develop coding concepts to help us gather both Time-series and Cross-sectional data (with the help of on-demand triggers).
Data generation and methods to send/log data
From a technical perspective, there are generally five main ways to transmit/store data. Each of these methods in turn require specific communication protocols that were developed for each method: for example, UDP and TCP/IP for WiFi/LAN. These are ordered generally in the overall implementation difficulty (although some have advantages in software/hardware over the rest):
- Serial Port transmission – simplest
- Localised data-logging (e.g. SD card)
- Other tethered transmission (e.g. CANBus)
- WiFi/LAN transmission (TCP/IP or UDP)
- Cellular transmission (TCP/IP or UDP)
A detailed tutorial on each of these methods is beyond the scope of this article, but these may be revisited in a future article around these transmission methods. For this tutorial on capturing sensor data via interval/demand, we will use the Serial Port as a starting point.
The Serial Port
Using the Serial Port to transmit data is the most basic and direct method for pushing sensor readings to a computer. We will use this to illustrate how to send data on an interval or on demand.
On the Arduino IDE platform, the built-in Serial Monitor offers a basic ‘printout’ of data coming in from an Arduino-compatible board. If you have been browsing the various recipes you would have come across Serial.print()
or Serial.println()
commands, along with the Serial.begin()
statements to establish the link between the microcontroller and the connected computer.
SERIAL PORT PRO TIP
When printing out lines of sensor readings, you can prepend a timestamp next to the reading without modifying your microcontroller code. In the Serial Monitor, click on the tiny clock icon on the right side of the panel:
This utilises the timestamp on your computer. You can then copy and paste this data into a spreadsheet, and ‘clean’ it up for further analysis.
Code
Use the below recipes as a starting point to assemble your time-series or cross-sectional data readings. Remember: it all depends on what purpose you have in mind for the data!
-
Coding for INTERVAL-based sensor data
In many of the Sensing Recipes on this website, the sample code provides examples of interval-based sensor reporting: sending readings at a fixed interval rate, e.g. every x seconds.
This is ideal for situations where the intervals of measurement are important to your understanding of the sensor data.
The code to do this is straightforward, and if all you need your microcontroller to do is to capture sensor data consistently over a fixed interval, the good old
delay
command (for the Arduino platform) works:Method A: Using the delay() command:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
// an example of using delay() to help with sensor readings // here we are using a simplified light-dependent resistor (LDR) circuit as the model // general comments have been minimised to focus on the Serial output. #define LDR_SENSE_PIN A0 int ldrVal = 0; void setup() { // runs once at startup digitalWrite(LED_PIN, LOW); // turn off the LED at startup Serial.begin(115200); // initializes the Serial port (for real-time inspection) } void loop() { // runs forever ldrVal = analogRead(LDR_SENSE_PIN); Serial.println(ldrVal); // this output is viewable via the Serial Monitor }
However, the use of
delay()
is not recommended for more advanced sketches, especially when you have to perform multiple tasks in succession. Eachdelay()
command literally stops the Arduino in its tracks, which cascades the delay effect throughout the rest of your code logic. A better approach is to make use of timer-based code libraries that make it easier to manage time-based intervals.This following example uses the Ticker library by Stefan Staub (you can install this via the Arduino IDE Library Manager, search for ‘Ticker’):
Method B: Using a timer-based code library (recommended):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
// an example of using Ticker to generate interval-based sensor readings #include "Ticker.h" // every time we use a library, we have to invoke it using the #include statement #define LDR_PIN A0 void pollSensor(); // this line 'reserves' the function name so it can be called later by Ticker Ticker myTimer(pollSensor, 500); // call the pollSensor function every 500ms void setup() { Serial.begin(115200); myTimer.start(); // start the timer in setup() } void loop() { myTimer.update(); // this updates the Ticker object and tells it to keep track of its timing // notice how there are no other lines here – the Ticker now 'manages' when to call pollSensor() } void pollSensor() { int ldrVal = analogRead(LDR_PIN); Serial.println(ldrVal); }
-
Coding for DEMAND-based sensor data
Demand-based sensor data that we collect from microcontrollers tends to be ‘triggered’ by a single ‘request’, such as the push of a button, or by a condition as defined in your code (e.g. when the distance sensor detects something within 50cm).
Such data triggers can still have a timestamp added to them (see the info box above for one way to do this). However, the more significant aspect of demand-based data is to collect one or typically more sensor readings in a single ‘trigger’.
Here is one code example using a pushbutton:
Pushbutton-activated sensor data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// in this scenario, let's assume we have TWO LDRs wired up #define LDR_A_PIN A0 #define LDR_B_PIN A1 #define TRIG_BTN 3 // we have a pushbutton wired to pin 3 void setup() { Serial.begin(115200); pinMode(TRIG_BTN, INPUT_PULLUP); // using INPUT_PULLUP stabilises the digital pin and prevents unwanted electrical noise/interference } void loop() { if(digitalRead(TRIG_BTN)==LOW) { sendReading(); } } void sendReading() { // custom function to send readings to Serial port int ldrAReading = analogRead(LDR_A_PIN); int ldrBReading = analogRead(LDR_B_PIN); // the following Serial lines give us the ability to output two readings on a single line, separated by a comma. Serial.print(ldrAReading); Serial.print(","); Serial.println(ldrBReading); }