Initial commit: ESP32 NextPM sensor reader
Educational version with comprehensive comments for students. - Reads PM1.0, PM2.5, and PM10 concentrations from NextPM sensor - Uses UART serial communication with checksum validation - Non-blocking timing with millis() - Removed particle counts, kept only mass concentrations - Extensively commented to help students understand serial protocols 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
.pio
|
||||||
|
.vscode/.browse.c_cpp.db*
|
||||||
|
.vscode/c_cpp_properties.json
|
||||||
|
.vscode/launch.json
|
||||||
|
.vscode/ipch
|
||||||
10
.vscode/extensions.json
vendored
Normal file
10
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||||
|
// for the documentation about the extensions.json format
|
||||||
|
"recommendations": [
|
||||||
|
"platformio.platformio-ide"
|
||||||
|
],
|
||||||
|
"unwantedRecommendations": [
|
||||||
|
"ms-vscode.cpptools-extension-pack"
|
||||||
|
]
|
||||||
|
}
|
||||||
91
CLAUDE.md
Normal file
91
CLAUDE.md
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
This is a PlatformIO-based ESP32 Arduino project for communicating with a NextPM (NPM) particulate matter sensor over serial connection. The project reads PM1, PM2.5, and PM10 measurements in both µg/m³ and particles per liter (pcs/L).
|
||||||
|
|
||||||
|
## Build and Development Commands
|
||||||
|
|
||||||
|
**Build the project:**
|
||||||
|
```bash
|
||||||
|
pio run
|
||||||
|
```
|
||||||
|
|
||||||
|
**Upload to ESP32:**
|
||||||
|
```bash
|
||||||
|
pio run --target upload
|
||||||
|
```
|
||||||
|
|
||||||
|
**Monitor serial output (115200 baud):**
|
||||||
|
```bash
|
||||||
|
pio device monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Build and upload in one command:**
|
||||||
|
```bash
|
||||||
|
pio run --target upload && pio device monitor
|
||||||
|
```
|
||||||
|
|
||||||
|
**Clean build files:**
|
||||||
|
```bash
|
||||||
|
pio run --target clean
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hardware Configuration
|
||||||
|
|
||||||
|
- **Board:** ESP32 dev board
|
||||||
|
- **Serial Configuration:**
|
||||||
|
- Debug serial: `Serial` (USB, 115200 baud)
|
||||||
|
- NPM serial: `Serial1` (UART1, 115200 baud, 8E1 parity)
|
||||||
|
- RX Pin: GPIO 39
|
||||||
|
- TX Pin: GPIO 32
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Serial Communication Protocol
|
||||||
|
|
||||||
|
The NextPM sensor uses a binary protocol with checksums. Communication follows a request-response pattern with specific message formats:
|
||||||
|
|
||||||
|
**Message Structure:**
|
||||||
|
- Header (2 bytes): Command identifier (e.g., `0x81 0x16`)
|
||||||
|
- State (1 byte): Device state bits
|
||||||
|
- Data (variable): Response payload
|
||||||
|
- Checksum (1 byte): Sum of all bytes mod 0x100 must equal 0
|
||||||
|
|
||||||
|
**State Machine Enums:**
|
||||||
|
The code uses multiple enums (`NPM_waiting_for_4`, `NPM_waiting_for_8`, `NPM_waiting_for_16`) to track parsing state for different response types (4, 8, or 16 bytes total).
|
||||||
|
|
||||||
|
### Key Functions
|
||||||
|
|
||||||
|
**Initialization (`powerOnTestNPM`):**
|
||||||
|
- Waits 15 seconds for sensor startup
|
||||||
|
- Queries sensor state
|
||||||
|
- Starts sensor if stopped
|
||||||
|
- Reads firmware version and temperature/humidity
|
||||||
|
|
||||||
|
**Measurement (`fetchSensorNPM_1min`):**
|
||||||
|
- Sends concentration command (1-minute averaged readings)
|
||||||
|
- Parses 16-byte response containing 6 values:
|
||||||
|
- N1, N2.5, N10 (particle counts as pcs/L)
|
||||||
|
- PM1, PM2.5, PM10 (mass concentrations as µg/m³ × 10)
|
||||||
|
- Validates checksum before accepting data
|
||||||
|
|
||||||
|
**Loop Behavior:**
|
||||||
|
- Reads sensor every 60 seconds (configurable via `interval` constant)
|
||||||
|
- Non-blocking timing using `millis()`
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
|
||||||
|
- `src/main.cpp`: Main application logic, NPM protocol implementation, sensor state machine
|
||||||
|
- `src/utils.h`: Protocol constants, enums, checksum validation prototypes
|
||||||
|
- `src/utils.cpp`: Utility functions for checksum validation and NPM command transmission
|
||||||
|
- `platformio.ini`: PlatformIO configuration for ESP32
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
- The sensor requires a 3-second timeout for serial responses
|
||||||
|
- All particle concentration values from the sensor are scaled (PM values by 10, temperature/humidity by 100)
|
||||||
|
- The `nextpmconnected` flag prevents infinite loops if sensor disconnects
|
||||||
|
- Comments are in French in some sections of the code
|
||||||
37
include/README
Normal file
37
include/README
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
This directory is intended for project header files.
|
||||||
|
|
||||||
|
A header file is a file containing C declarations and macro definitions
|
||||||
|
to be shared between several project source files. You request the use of a
|
||||||
|
header file in your project source file (C, C++, etc) located in `src` folder
|
||||||
|
by including it, with the C preprocessing directive `#include'.
|
||||||
|
|
||||||
|
```src/main.c
|
||||||
|
|
||||||
|
#include "header.h"
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Including a header file produces the same results as copying the header file
|
||||||
|
into each source file that needs it. Such copying would be time-consuming
|
||||||
|
and error-prone. With a header file, the related declarations appear
|
||||||
|
in only one place. If they need to be changed, they can be changed in one
|
||||||
|
place, and programs that include the header file will automatically use the
|
||||||
|
new version when next recompiled. The header file eliminates the labor of
|
||||||
|
finding and changing all the copies as well as the risk that a failure to
|
||||||
|
find one copy will result in inconsistencies within a program.
|
||||||
|
|
||||||
|
In C, the convention is to give header files names that end with `.h'.
|
||||||
|
|
||||||
|
Read more about using header files in official GCC documentation:
|
||||||
|
|
||||||
|
* Include Syntax
|
||||||
|
* Include Operation
|
||||||
|
* Once-Only Headers
|
||||||
|
* Computed Includes
|
||||||
|
|
||||||
|
https://gcc.gnu.org/onlinedocs/cpp/Header-Files.html
|
||||||
46
lib/README
Normal file
46
lib/README
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
|
||||||
|
This directory is intended for project specific (private) libraries.
|
||||||
|
PlatformIO will compile them to static libraries and link into the executable file.
|
||||||
|
|
||||||
|
The source code of each library should be placed in a separate directory
|
||||||
|
("lib/your_library_name/[Code]").
|
||||||
|
|
||||||
|
For example, see the structure of the following example libraries `Foo` and `Bar`:
|
||||||
|
|
||||||
|
|--lib
|
||||||
|
| |
|
||||||
|
| |--Bar
|
||||||
|
| | |--docs
|
||||||
|
| | |--examples
|
||||||
|
| | |--src
|
||||||
|
| | |- Bar.c
|
||||||
|
| | |- Bar.h
|
||||||
|
| | |- library.json (optional. for custom build options, etc) https://docs.platformio.org/page/librarymanager/config.html
|
||||||
|
| |
|
||||||
|
| |--Foo
|
||||||
|
| | |- Foo.c
|
||||||
|
| | |- Foo.h
|
||||||
|
| |
|
||||||
|
| |- README --> THIS FILE
|
||||||
|
|
|
||||||
|
|- platformio.ini
|
||||||
|
|--src
|
||||||
|
|- main.c
|
||||||
|
|
||||||
|
Example contents of `src/main.c` using Foo and Bar:
|
||||||
|
```
|
||||||
|
#include <Foo.h>
|
||||||
|
#include <Bar.h>
|
||||||
|
|
||||||
|
int main (void)
|
||||||
|
{
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
The PlatformIO Library Dependency Finder will find automatically dependent
|
||||||
|
libraries by scanning project source files.
|
||||||
|
|
||||||
|
More information about PlatformIO Library Dependency Finder
|
||||||
|
- https://docs.platformio.org/page/librarymanager/ldf.html
|
||||||
15
platformio.ini
Normal file
15
platformio.ini
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
; PlatformIO Project Configuration File
|
||||||
|
;
|
||||||
|
; Build options: build flags, source filter
|
||||||
|
; Upload options: custom upload port, speed and extra flags
|
||||||
|
; Library options: dependencies, extra library storages
|
||||||
|
; Advanced options: extra scripting
|
||||||
|
;
|
||||||
|
; Please visit documentation for the other options and examples
|
||||||
|
; https://docs.platformio.org/page/projectconf.html
|
||||||
|
|
||||||
|
[env:esp32]
|
||||||
|
platform = espressif32
|
||||||
|
board = esp32dev
|
||||||
|
monitor_speed = 115200
|
||||||
|
framework = arduino
|
||||||
318
src/main.cpp
Normal file
318
src/main.cpp
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
/*
|
||||||
|
* ESP32 NextPM Particulate Matter Sensor Reader
|
||||||
|
*
|
||||||
|
* This program reads PM (Particulate Matter) concentration data from a NextPM sensor
|
||||||
|
* connected to an ESP32 via serial communication (UART).
|
||||||
|
*
|
||||||
|
* The sensor measures three types of particulate matter:
|
||||||
|
* - PM1.0: Particles with diameter < 1.0 micrometers
|
||||||
|
* - PM2.5: Particles with diameter < 2.5 micrometers
|
||||||
|
* - PM10: Particles with diameter < 10 micrometers
|
||||||
|
*
|
||||||
|
* Values are reported in µg/m³ (micrograms per cubic meter)
|
||||||
|
* Readings are averaged over 60 seconds and updated every 10 seconds
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* HARDWARE CONFIGURATION
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
// Serial port definitions
|
||||||
|
// The ESP32 has multiple serial ports. We use:
|
||||||
|
// - Serial: USB connection for debugging/output (Serial Monitor)
|
||||||
|
// - Serial1: UART1 for communicating with the NextPM sensor
|
||||||
|
#define serialNPM (Serial1)
|
||||||
|
|
||||||
|
// GPIO pins for sensor communication
|
||||||
|
#define PM_SERIAL_RX 39 // Receive data from sensor on GPIO 39
|
||||||
|
#define PM_SERIAL_TX 32 // Transmit data to sensor on GPIO 32
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* TIMING CONFIGURATION
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
// Variables for non-blocking timing
|
||||||
|
// We use millis() instead of delay() to avoid blocking the program
|
||||||
|
unsigned long previousMillis = 0; // Stores the last time we read the sensor
|
||||||
|
const long interval = 10000; // Read every 10 seconds (10000 milliseconds)
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* DATA STORAGE
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
// Global variables to store the latest PM measurements
|
||||||
|
float pm1_ugm3 = 0.0; // PM1.0 concentration in µg/m³
|
||||||
|
float pm25_ugm3 = 0.0; // PM2.5 concentration in µg/m³
|
||||||
|
float pm10_ugm3 = 0.0; // PM10 concentration in µg/m³
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* HELPER FUNCTIONS
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the checksum of a 16-byte message from the sensor
|
||||||
|
*
|
||||||
|
* The NextPM sensor uses a simple checksum algorithm for data integrity:
|
||||||
|
* Sum all 16 bytes together, and the result modulo 256 should equal 0.
|
||||||
|
*
|
||||||
|
* @param data Array of 16 bytes to validate
|
||||||
|
* @return true if checksum is valid, false otherwise
|
||||||
|
*/
|
||||||
|
bool checksum_valid(const uint8_t (&data)[16]) {
|
||||||
|
uint8_t sum = 0;
|
||||||
|
// Add up all bytes in the message
|
||||||
|
for (int i = 0; i < 16; i++) {
|
||||||
|
sum += data[i];
|
||||||
|
}
|
||||||
|
// Valid if sum modulo 256 equals 0
|
||||||
|
// (sum % 0x100 is the same as sum % 256)
|
||||||
|
return (sum % 0x100 == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send the command to request concentration data from the sensor
|
||||||
|
*
|
||||||
|
* The command is a 3-byte sequence:
|
||||||
|
* - 0x81: Command prefix (tells sensor a command is coming)
|
||||||
|
* - 0x12: Concentration request command code
|
||||||
|
* - 0x6D: Checksum byte (makes the sum of all 3 bytes valid)
|
||||||
|
*
|
||||||
|
* This requests PM values averaged over 1 minute
|
||||||
|
*/
|
||||||
|
void send_concentration_command() {
|
||||||
|
const uint8_t cmd[] = {0x81, 0x12, 0x6D};
|
||||||
|
serialNPM.write(cmd, 3); // Send all 3 bytes to the sensor
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print raw byte data in hexadecimal format for debugging
|
||||||
|
*
|
||||||
|
* This is useful when troubleshooting communication issues with the sensor.
|
||||||
|
* Example output: Raw data: 0x81, 0x12, 0x00, 0x1A, ...
|
||||||
|
*
|
||||||
|
* @param data Array of bytes to print
|
||||||
|
* @param size Number of bytes in the array
|
||||||
|
*/
|
||||||
|
void print_data(uint8_t data[], size_t size) {
|
||||||
|
Serial.print("Raw data: ");
|
||||||
|
for (size_t i = 0; i < size; i++) {
|
||||||
|
Serial.print("0x");
|
||||||
|
if (data[i] < 0x10) Serial.print("0"); // Add leading zero for single-digit hex
|
||||||
|
Serial.print(data[i], HEX);
|
||||||
|
if (i != size - 1) Serial.print(", "); // Add comma between bytes
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* MAIN SENSOR READING FUNCTION
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read and parse PM concentration data from the NextPM sensor
|
||||||
|
*
|
||||||
|
* This function performs the following steps:
|
||||||
|
* 1. Send a command to the sensor requesting concentration data
|
||||||
|
* 2. Wait for and receive the sensor's response (16 bytes total)
|
||||||
|
* 3. Validate the data using checksum verification
|
||||||
|
* 4. Extract and convert PM values
|
||||||
|
* 5. Display results on the serial monitor
|
||||||
|
*
|
||||||
|
* The sensor response format (16 bytes):
|
||||||
|
* [0-1] Header: 0x81 0x12
|
||||||
|
* [2] State: Sensor status byte
|
||||||
|
* [3-14] Data: 6 values × 2 bytes each (N1, N2.5, N10, PM1, PM2.5, PM10)
|
||||||
|
* [15] Checksum: Validation byte
|
||||||
|
*/
|
||||||
|
void read_concentration() {
|
||||||
|
Serial.println("\n--- Reading NextPM Concentrations ---");
|
||||||
|
|
||||||
|
// STEP 1: Send command to sensor
|
||||||
|
send_concentration_command();
|
||||||
|
|
||||||
|
// STEP 2: Wait for response with timeout protection
|
||||||
|
// The sensor typically responds within 1 second, but we wait up to 3 seconds
|
||||||
|
unsigned long timeout = millis();
|
||||||
|
while (!serialNPM.available() && millis() - timeout < 3000) {
|
||||||
|
delay(10); // Small delay to avoid hogging the CPU
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we received any data
|
||||||
|
if (!serialNPM.available()) {
|
||||||
|
Serial.println("ERROR: No response from sensor");
|
||||||
|
return; // Exit function if no response
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 3: Look for the response header (0x81 0x12)
|
||||||
|
// The header identifies this as a concentration response message
|
||||||
|
const uint8_t header[2] = {0x81, 0x12};
|
||||||
|
if (!serialNPM.find(header, 2)) {
|
||||||
|
Serial.println("ERROR: Header not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 4: Read the state byte
|
||||||
|
// This byte contains status information about the sensor
|
||||||
|
uint8_t state;
|
||||||
|
if (serialNPM.readBytes(&state, 1) != 1) {
|
||||||
|
Serial.println("ERROR: Failed to read state");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 5: Read the 12 data bytes
|
||||||
|
// These contain 6 values (2 bytes each): N1, N2.5, N10, PM1, PM2.5, PM10
|
||||||
|
uint8_t data[12];
|
||||||
|
if (serialNPM.readBytes(data, 12) != 12) {
|
||||||
|
Serial.println("ERROR: Failed to read data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 6: Read the checksum byte
|
||||||
|
// This is used to verify data integrity
|
||||||
|
uint8_t checksum;
|
||||||
|
if (serialNPM.readBytes(&checksum, 1) != 1) {
|
||||||
|
Serial.println("ERROR: Failed to read checksum");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 7: Reconstruct the complete 16-byte message for validation
|
||||||
|
uint8_t full_msg[16];
|
||||||
|
full_msg[0] = header[0]; // 0x81
|
||||||
|
full_msg[1] = header[1]; // 0x12
|
||||||
|
full_msg[2] = state; // State byte
|
||||||
|
memcpy(&full_msg[3], data, 12); // Copy 12 data bytes
|
||||||
|
full_msg[15] = checksum; // Checksum byte
|
||||||
|
|
||||||
|
// STEP 8: Validate the checksum
|
||||||
|
if (!checksum_valid(full_msg)) {
|
||||||
|
Serial.println("ERROR: Invalid checksum");
|
||||||
|
print_data(full_msg, 16); // Print raw data for debugging
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// STEP 9: Parse the concentration data from the 12-byte response
|
||||||
|
//
|
||||||
|
// Data layout in the 12 bytes:
|
||||||
|
// Bytes 0-1: N1.0 particle count (we skip this)
|
||||||
|
// Bytes 2-3: N2.5 particle count (we skip this)
|
||||||
|
// Bytes 4-5: N10 particle count (we skip this)
|
||||||
|
// Bytes 6-7: PM1.0 concentration ← We extract this
|
||||||
|
// Bytes 8-9: PM2.5 concentration ← We extract this
|
||||||
|
// Bytes 10-11: PM10 concentration ← We extract this
|
||||||
|
//
|
||||||
|
// Each value is a 16-bit unsigned integer (2 bytes) in big-endian format
|
||||||
|
// (high byte first, then low byte)
|
||||||
|
|
||||||
|
// Extract PM1.0 concentration (bytes 6-7)
|
||||||
|
// word() combines two bytes into a 16-bit integer (high byte, low byte)
|
||||||
|
uint16_t pm1_raw = word(data[6], data[7]);
|
||||||
|
|
||||||
|
// Extract PM2.5 concentration (bytes 8-9)
|
||||||
|
uint16_t pm25_raw = word(data[8], data[9]);
|
||||||
|
|
||||||
|
// Extract PM10 concentration (bytes 10-11)
|
||||||
|
uint16_t pm10_raw = word(data[10], data[11]);
|
||||||
|
|
||||||
|
// STEP 10: Convert raw values to actual concentrations
|
||||||
|
// The sensor sends values multiplied by 10 to preserve one decimal place
|
||||||
|
// Example: A reading of 25.3 µg/m³ is sent as 253
|
||||||
|
// So we divide by 10.0 to get the real value in µg/m³
|
||||||
|
pm1_ugm3 = pm1_raw / 10.0;
|
||||||
|
pm25_ugm3 = pm25_raw / 10.0;
|
||||||
|
pm10_ugm3 = pm10_raw / 10.0;
|
||||||
|
|
||||||
|
// STEP 11: Display the results to the serial monitor
|
||||||
|
Serial.println("\n=== Particulate Matter Concentrations ===");
|
||||||
|
|
||||||
|
Serial.print("PM1.0: ");
|
||||||
|
Serial.print(pm1_ugm3, 1); // Print with 1 decimal place
|
||||||
|
Serial.println(" µg/m³");
|
||||||
|
|
||||||
|
Serial.print("PM2.5: ");
|
||||||
|
Serial.print(pm25_ugm3, 1);
|
||||||
|
Serial.println(" µg/m³");
|
||||||
|
|
||||||
|
Serial.print("PM10: ");
|
||||||
|
Serial.print(pm10_ugm3, 1);
|
||||||
|
Serial.println(" µg/m³");
|
||||||
|
|
||||||
|
Serial.println("======================================\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* ARDUINO SETUP FUNCTION
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup function - runs once when the ESP32 starts or resets
|
||||||
|
*
|
||||||
|
* This function initializes:
|
||||||
|
* 1. USB serial communication for debugging/output
|
||||||
|
* 2. UART serial communication with the NextPM sensor
|
||||||
|
* 3. Waits for the sensor to power up and stabilize
|
||||||
|
*/
|
||||||
|
void setup() {
|
||||||
|
// ===== Initialize USB Serial for debugging =====
|
||||||
|
// This allows us to see output in the Arduino Serial Monitor
|
||||||
|
Serial.begin(115200); // 115200 baud rate
|
||||||
|
delay(2000); // Wait 2 seconds for serial connection to stabilize
|
||||||
|
|
||||||
|
// Print startup message
|
||||||
|
Serial.println("\n\n=== NextPM Sensor Reader ===");
|
||||||
|
Serial.println("Simplified version - Concentration readings only");
|
||||||
|
Serial.println("Averaging: 60 seconds, Update: every 10 seconds\n");
|
||||||
|
|
||||||
|
// ===== Initialize Sensor Serial Communication =====
|
||||||
|
// SERIAL_8E1 means:
|
||||||
|
// - 8 data bits
|
||||||
|
// - Even parity (error checking bit)
|
||||||
|
// - 1 stop bit
|
||||||
|
// This is the format required by the NextPM sensor
|
||||||
|
serialNPM.begin(115200, SERIAL_8E1, PM_SERIAL_RX, PM_SERIAL_TX);
|
||||||
|
|
||||||
|
// Set timeout for serial read operations to 3 seconds
|
||||||
|
// If the sensor doesn't respond within 3 seconds, read operations will fail
|
||||||
|
serialNPM.setTimeout(3000);
|
||||||
|
|
||||||
|
// ===== Wait for sensor to power up =====
|
||||||
|
// The NextPM sensor requires about 15 seconds to initialize after power-on
|
||||||
|
// This is critical - reading too early will fail
|
||||||
|
Serial.println("Waiting 15 seconds for sensor initialization...");
|
||||||
|
delay(15000);
|
||||||
|
|
||||||
|
Serial.println("Sensor ready. Starting measurements...\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*****************************************************************
|
||||||
|
* ARDUINO MAIN LOOP FUNCTION
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop function - runs repeatedly after setup() completes
|
||||||
|
*
|
||||||
|
* This function implements non-blocking timing to read the sensor
|
||||||
|
* every 10 seconds without using delay(), which would freeze the program.
|
||||||
|
*
|
||||||
|
* Non-blocking timing allows the ESP32 to do other tasks between readings
|
||||||
|
* (e.g., responding to network requests, processing interrupts, etc.)
|
||||||
|
*/
|
||||||
|
void loop() {
|
||||||
|
// Get the current time in milliseconds since the ESP32 started
|
||||||
|
// millis() wraps around after ~49 days, but our math handles this correctly
|
||||||
|
unsigned long currentMillis = millis();
|
||||||
|
|
||||||
|
// Check if enough time has passed since the last reading
|
||||||
|
// interval = 10000 milliseconds = 10 seconds
|
||||||
|
if (currentMillis - previousMillis >= interval) {
|
||||||
|
// Update the timestamp for the next reading
|
||||||
|
previousMillis = currentMillis;
|
||||||
|
|
||||||
|
// Read and display sensor data
|
||||||
|
read_concentration();
|
||||||
|
}
|
||||||
|
|
||||||
|
// The loop continues to run, checking the time on every iteration
|
||||||
|
// This is much more efficient than using delay() which would block execution
|
||||||
|
}
|
||||||
11
test/README
Normal file
11
test/README
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
This directory is intended for PlatformIO Test Runner and project tests.
|
||||||
|
|
||||||
|
Unit Testing is a software testing method by which individual units of
|
||||||
|
source code, sets of one or more MCU program modules together with associated
|
||||||
|
control data, usage procedures, and operating procedures, are tested to
|
||||||
|
determine whether they are fit for use. Unit testing finds problems early
|
||||||
|
in the development cycle.
|
||||||
|
|
||||||
|
More information about PlatformIO Unit Testing:
|
||||||
|
- https://docs.platformio.org/en/latest/advanced/unit-testing/index.html
|
||||||
Reference in New Issue
Block a user