BUT Mesures Physiques · IUT Aix-Marseille · 2026-2027

Exemple : lire le NextPM toutes les 10 secondes

Le programme de base pour le capteur de particules : toutes les 10 secondes, il lit l'état du NextPM, puis les PM1, PM2.5 et PM10 (moyennes 10 s), la température et l'humidité, et affiche tout sur le moniteur série. Même code pour ModuleAir et NebuleAir.

Code de l'exemple du guide IUT-AMU-04 « Lire le capteur de particules NextPM », v1.0 du 2026-10-01. Les explications sont dans le PDF.

Mise en route

  1. Téléchargez le projet (bouton vert), décompressez-le. Dans VS Code, onglet PlatformIO, Open Project, choisissez le dossier décompressé.
    Ou créez un projet PlatformIO (carte Espressif ESP32 Dev Module, framework Arduino) et remplacez ses fichiers par ceux ci-dessous, avec le bouton Copier.
  2. Vérifiez les broches du NextPM sur votre carte (tableau du guide, partie 2) : sur les cartes 2026, RX = GPIO 39 et TX = GPIO 32.
  3. Téléversez (Upload), puis ouvrez le moniteur série (Monitor, 115200 bauds).

Le capteur ne répond pas (erreur Modbus 0xE2) ? Vérifiez la parité : SERIAL_8E1, pas SERIAL_8N1. Les deux premières minutes, les valeurs sont sous-estimées : le ventilateur monte en régime.

Le projet

Télécharger le projet IUT-AMU-04-exemple-v1.0.zip
platformio.ini
[env:esp32]
platform = espressif32 @ 7.1.3
board = esp32dev
framework = arduino
monitor_speed = 115200
lib_deps =
    4-20ma/ModbusMaster @ 2.0.1
src/main.cpp
// Exemple : lire le NextPM toutes les 10 s et afficher les mesures et son état.
#include <Arduino.h>
#include <ModbusMaster.h>

// Branchement du NextPM sur l'UART n°1 de l'ESP32 (cartes 2026 : ModuleAir et NebuleAir)
#define NEXTPM_RX 39   // l'ESP32 reçoit sur GPIO 39 (fil TX du capteur)
#define NEXTPM_TX 32   // l'ESP32 émet sur GPIO 32 (fil RX du capteur)

// Registres Modbus du NextPM
#define REG_STATUS 0x13   // état du capteur : codes d'erreur
#define REG_PM1    0x38   // moyennes 10 s (tests) ; en production, moyennes 60 s :
#define REG_PM25   0x3A   //   PM1 = 0x44, PM2.5 = 0x46, PM10 = 0x48
#define REG_PM10   0x3C
#define REG_HUM    0x6A   // humidité interne, en centièmes de %
#define REG_TEMP   0x6B   // température interne, en centièmes de °C

// Signification des 8 bits du registre d'état (bit 0 en premier)
const char* STATUS_BITS[8] = {
  "veille", "mode degrade", "demarrage en cours", "humidite trop forte",
  "erreur capteur T/HR", "erreur ventilateur", "erreur memoire", "erreur laser"
};
// Bits qui rendent la mesure inutilisable : démarrage, humidité, ventilateur, laser
#define STATUS_CRITICAL 0xAC

HardwareSerial nextpmSerial(1);   // UART n°1 de l'ESP32
ModbusMaster nextpm;

// Une valeur PM occupe 2 registres de 16 bits (poids faible d'abord), en
// millièmes de µg/m³. Renvoie -1 si le capteur n'a pas répondu.
float readPM(uint16_t reg) {
  if (nextpm.readHoldingRegisters(reg, 2) != nextpm.ku8MBSuccess) {
    return -1;
  }
  uint32_t raw = ((uint32_t)nextpm.getResponseBuffer(1) << 16) | nextpm.getResponseBuffer(0);
  return raw / 1000.0;
}

void setup() {
  Serial.begin(115200);
  nextpmSerial.begin(115200, SERIAL_8E1, NEXTPM_RX, NEXTPM_TX);  // 8E1 : parité paire !
  nextpm.begin(1, nextpmSerial);                                  // adresse Modbus 1
}

void loop() {
  // 1) Lire l'état en premier : il dit si le capteur répond, et si la mesure est fiable.
  uint8_t result = nextpm.readHoldingRegisters(REG_STATUS, 1);
  if (result != nextpm.ku8MBSuccess) {
    Serial.print("Pas de reponse du NextPM (erreur Modbus 0x");
    Serial.print(result, HEX);
    Serial.println(") : branchement ? broches ? parite 8E1 ?");
    delay(10000);
    return;
  }
  uint8_t status = nextpm.getResponseBuffer(0) & 0xFF;

  Serial.print("Etat : ");
  Serial.print(status);
  if (status == 0) {
    Serial.println(" (tout va bien)");
  } else {
    for (int bit = 0; bit < 8; bit++) {
      if (status & (1 << bit)) {
        Serial.print(" [");
        Serial.print(STATUS_BITS[bit]);
        Serial.print("]");
      }
    }
    Serial.println();
  }

  // 2) Mesure inutilisable : on ne lit pas les PM.
  if (status & STATUS_CRITICAL) {
    Serial.println("Mesure non fiable, ignoree");
    delay(10000);
    return;
  }

  // 3) Les particules (moyennes sur 10 s) et la température / humidité internes
  float pm1 = readPM(REG_PM1);
  float pm25 = readPM(REG_PM25);
  float pm10 = readPM(REG_PM10);
  Serial.print("PM1 = ");    Serial.print(pm1);
  Serial.print("  PM2.5 = "); Serial.print(pm25);
  Serial.print("  PM10 = ");  Serial.print(pm10);
  Serial.println(" ug/m3");

  if (nextpm.readHoldingRegisters(REG_TEMP, 1) == nextpm.ku8MBSuccess) {
    float temp = (int16_t)nextpm.getResponseBuffer(0) / 100.0;  // peut être négative
    Serial.print("T = "); Serial.print(temp); Serial.print(" C  ");
  }
  if (nextpm.readHoldingRegisters(REG_HUM, 1) == nextpm.ku8MBSuccess) {
    float hum = nextpm.getResponseBuffer(0) / 100.0;
    Serial.print("HR = "); Serial.print(hum); Serial.print(" %");
  }
  Serial.println();

  delay(10000);  // le NextPM met à jour ses valeurs toutes les 10 s
}