Napisz program na kalibrację czujnika SCD41 na arduino MEGA
Skopiuj do Arduino IDE, ustaw Monitor portu szeregowego na 115200 baud.
/*
SCD41 – program kalibracyjny na Arduino MEGA 2560
Funkcje:
1) FRC (Forced Recalibration) do znanego CO2 (domyślnie 400 ppm)
2) ASC – włącz/wyłącz
3) Kompensacja wysokości n.p.m.
4) Kompensacja ciśnienia atmosferycznego
5) Kompensacja temperatury (offset)
6) Reset fabryczny
7) Test własny (self-test)
8) Odczyt ciągły danych (co 5 s)
9) Zapis ustawień do NVM (persistSettings)
Wymaga biblioteki: Sensirion I2C SCD4x
*/
#include <Wire.h>
#include <SensirionI2CScd4x.h>
SensirionI2CScd4x scd4x;
static const uint16_t DEFAULT_FRC_PPM = 400; // świeże powietrze
bool measuring = false;
void printHex16(uint16_t v) {
if (v < 0x1000) Serial.print('0');
if (v < 0x0100) Serial.print('0');
if (v < 0x0010) Serial.print('0');
Serial.print(v, HEX);
}
String readLine(uint32_t timeoutMs = 15000) {
String s;
uint32_t t0 = millis();
while (millis() - t0 < timeoutMs) {
while (Serial.available()) {
char c = (char)Serial.read();
if (c == '\r' || c == '\n') {
if (s.length()) return s;
} else {
s += c;
}
}
delay(5);
}
return s;
}
long readLongOrDefault(long def, long minV, long maxV, const char* prompt) {
Serial.print(prompt);
Serial.print(" [ENTER = ");
Serial.print(def);
Serial.println("]: ");
String s = readLine();
s.trim();
if (!s.length()) return def;
long v = s.toInt();
if (v < minV) v = minV;
if (v > maxV) v = maxV;
return v;
}
float readFloatOrDefault(float def, float minV, float maxV, const char* prompt) {
Serial.print(prompt);
Serial.print(" [ENTER = ");
Serial.print(def, 2);
Serial.println("]: ");
String s = readLine();
s.trim();
if (!s.length()) return def;
float v = s.toFloat();
if (v < minV) v = minV;
if (v > maxV) v = maxV;
return v;
}
void printErrorIfAny(uint16_t error, const char* what) {
if (error) {
char msg[256];
errorToString(error, msg, sizeof(msg));
Serial.print("Błąd: ");
Serial.print(what);
Serial.print(" -> ");
Serial.println(msg);
}
}
void showSerialNumber() {
uint16_t s0, s1, s2;
uint16_t err = scd4x.getSerialNumber(s0, s1, s2);
if (!err) {
Serial.print("Numer seryjny: 0x");
printHex16(s0); printHex16(s1); printHex16(s2);
Serial.println();
} else {
printErrorIfAny(err, "getSerialNumber()");
}
}
void startMeasIfNeeded() {
if (!measuring) {
uint16_t err = scd4x.startPeriodicMeasurement();
printErrorIfAny(err, "startPeriodicMeasurement()");
if (!err) measuring = true;
}
}
void stopMeasIfNeeded() {
if (measuring) {
uint16_t err = scd4x.stopPeriodicMeasurement();
printErrorIfAny(err, "stopPeriodicMeasurement()");
if (!err) measuring = false;
}
}
void waitWithCountdown(uint32_t seconds, const char* info) {
Serial.print(info);
Serial.print(" (");
Serial.print(seconds);
Serial.println(" s)...");
for (uint32_t i = 0; i < seconds; ++i) {
if ((seconds - i) % 10 == 0 || (seconds - i) <= 10) {
Serial.print("Pozostało: ");
Serial.print(seconds - i);
Serial.println(" s");
}
delay(1000);
}
}
void doFRC() {
Serial.println("\n=== Wymuszona kalibracja (FRC) ===");
Serial.println("Upewnij się, że czujnik jest na świeżym powietrzu (~400 ppm),");
Serial.println("bez nagłych zmian temperatury/wilgotności.");
uint16_t target = (uint16_t)readLongOrDefault(DEFAULT_FRC_PPM, 350, 2000, "Podaj referencję CO2 w ppm");
long stabMin = readLongOrDefault(5, 3, 30, "Czas stabilizacji na referencji [min]");
startMeasIfNeeded();
waitWithCountdown((uint32_t)(stabMin * 60UL), "Stabilizacja przed FRC");
stopMeasIfNeeded();
uint16_t frcCorrection = 0;
uint16_t err = scd4x.performForcedRecalibration(target, frcCorrection);
if (err) {
printErrorIfAny(err, "performForcedRecalibration()");
} else if (frcCorrection == 0xFFFF) {
Serial.println("Kalibracja FRC nieudana (warunki niestabilne lub zła referencja).");
} else {
// Korekta jest podawana względem offsetu (wartość signed zakodowana wokół 0x8000)
int32_t corr_ppm = (int32_t)frcCorrection - 0x8000;
Serial.print("FRC zakończona. Korekta: ");
Serial.print(corr_ppm);
Serial.println(" ppm");
String ans;
Serial.print("Zapisać do NVM (persistSettings)? [t/N]: ");
ans = readLine();
ans.toLowerCase();
if (ans == "t" || ans == "tak" || ans == "y") {
err = scd4x.persistSettings();
printErrorIfAny(err, "persistSettings()");
if (!err) Serial.println("Zapisano ustawienia do NVM.");
}
}
startMeasIfNeeded();
}
void toggleASC() {
bool enabled = true;
uint16_t err = scd4x.getAutomaticSelfCalibrationEnabled(enabled);
if (!err) {
Serial.print("ASC aktualnie: ");
Serial.println(enabled ? "WL" : "WYL");
}
String ans;
Serial.print("Włącz ASC? [t/N]: ");
ans = readLine(); ans.toLowerCase();
bool toEnable = (ans == "t" || ans == "tak" || ans == "y");
err = scd4x.setAutomaticSelfCalibrationEnabled(toEnable);
printErrorIfAny(err, "setAutomaticSelfCalibrationEnabled()");
if (!err) Serial.println(toEnable ? "ASC włączone." : "ASC wyłączone.");
}
void setAltitude() {
long alt = readLongOrDefault(100, 0, 30000, "Podaj wysokość n.p.m. [m]");
uint16_t err = scd4x.setSensorAltitude((uint16_t)alt);
printErrorIfAny(err, "setSensorAltitude()");
if (!err) Serial.println("Ustawiono kompensację wysokości.");
}
void setAmbientPressure() {
long p = readLongOrDefault(1013, 700, 1200, "Podaj ciśnienie atmosferyczne [mbar]");
uint16_t err = scd4x.setAmbientPressure((uint16_t)p);
printErrorIfAny(err, "setAmbientPressure()");
if (!err) Serial.println("Zastosowano kompensację ciśnienia.");
}
void setTempOffset() {
Serial.println("Kompensacja temperatury:");
Serial.println("- Opcja A: podaj gotowy offset [°C] (np. +1.5)");
Serial.println("- Opcja B: podaj temperaturę referencyjną, a program policzy offset.");
String mode;
Serial.print("Wybierz tryb A/B [ENTER=A]: ");
mode = readLine(); mode.toLowerCase();
if (mode == "b") {
// pobierz szybki pomiar temperatury
startMeasIfNeeded();
uint16_t ready = 0;
uint16_t err = scd4x.getDataReadyStatus(ready);
if (!err && (ready & 0x07FF)) {
uint16_t co2; float t, rh;
err = scd4x.readMeasurement(co2, t, rh);
if (!err && co2 != 0) {
float tref = readFloatOrDefault(t, -20.0f, 60.0f, "Podaj temperaturę referencyjną [°C]");
float offset = tref - t;
err = scd4x.setTemperatureOffset(offset);
printErrorIfAny(err, "setTemperatureOffset()");
if (!err) {
Serial.print("Ustawiono offset T = ");
Serial.print(offset, 2);
Serial.println(" °C");
}
return;
}
}
Serial.println("Nie udało się pobrać aktualnej temperatury. Spróbuj ponownie.");
} else {
float offs = readFloatOrDefault(0.0f, -20.0f, 20.0f, "Podaj offset temperatury [°C]");
uint16_t err = scd4x.setTemperatureOffset(offs);
printErrorIfAny(err, "setTemperatureOffset()");
if (!err) {
Serial.print("Ustawiono offset T = ");
Serial.print(offs, 2);
Serial.println(" °C");
}
}
}
void doFactoryReset() {
Serial.println("Uwaga: reset fabryczny przywraca ustawienia domyślne.");
String ans;
Serial.print("Kontynuować? [t/N]: ");
ans = readLine(); ans.toLowerCase();
if (ans == "t" || ans == "tak" || ans == "y") {
stopMeasIfNeeded();
uint16_t err = scd4x.performFactoryReset();
printErrorIfAny(err, "performFactoryReset()");
delay(100);
err = scd4x.reInit(); // odczyt ustawień z NVM i re-inicjalizacja
printErrorIfAny(err, "reInit()");
startMeasIfNeeded();
}
}
void doSelfTest() {
Serial.println("Uruchamiam test własny...");
stopMeasIfNeeded();
uint16_t testResult = 0;
uint16_t err = scd4x.performSelfTest(testResult);
if (err) {
printErrorIfAny(err, "performSelfTest()");
} else {
if (testResult == 0) {
Serial.println("Self-test: OK");
} else {
Serial.print("Self-test: kod błędu = 0x");
printHex16(testResult);
Serial.println();
}
}
startMeasIfNeeded();
}
void readOnceIfReady() {
uint16_t ready = 0;
uint16_t err = scd4x.getDataReadyStatus(ready);
if (err) {
printErrorIfAny(err, "getDataReadyStatus()");
return;
}
if ((ready & 0x07FF) == 0) {
Serial.println("Dane niegotowe.");
return;
}
uint16_t co2; float t, rh;
err = scd4x.readMeasurement(co2, t, rh);
if (err) {
printErrorIfAny(err, "readMeasurement()");
return;
}
if (co2 == 0) {
Serial.println("Odczyt niepoprawny (CO2=0) – poczekaj na kolejną próbkę.");
return;
}
Serial.print("CO2: "); Serial.print(co2); Serial.print(" ppm | ");
Serial.print("T: "); Serial.print(t, 2); Serial.print(" °C | ");
Serial.print("RH: "); Serial.print(rh, 2); Serial.println(" %");
}
void persist() {
uint16_t err = scd4x.persistSettings();
printErrorIfAny(err, "persistSettings()");
if (!err) Serial.println("Zapisano bieżące ustawienia do NVM.");
}
void printMenu() {
Serial.println("\n========== MENU KALIBRACJI SCD41 ==========");
Serial.println("1 - FRC (wymuszona kalibracja do znanego CO2)");
Serial.println("2 - ASC (włącz/wyłącz)");
Serial.println("3 - Ustaw wysokość n.p.m. [m]");
Serial.println("4 - Ustaw ciśnienie atmosferyczne [mbar]");
Serial.println("5 - Ustaw offset temperatury [°C]");
Serial.println("6 - Reset fabryczny");
Serial.println("7 - Test własny (self-test)");
Serial.println("8 - Odczyt pojedynczy (gdy gotowe)");
Serial.println("9 - Zapisz ustawienia (persistSettings)");
Serial.println("s - Start pomiaru okresowego");
Serial.println("t - Stop pomiaru okresowego");
Serial.println("m - Wyświetl to menu");
Serial.println("q - Odczyt ciągły co 5 s (ESC aby przerwać)");
Serial.println("===========================================");
Serial.print("Wybór: ");
}
void continuousLoop() {
Serial.println("Odczyt ciągły – naciśnij ESC, by wyjść.");
startMeasIfNeeded();
while (true) {
if (Serial.available()) {
int c = Serial.read();
if (c == 27) { // ESC
Serial.println("\nKoniec odczytu ciągłego.");
break;
}
}
readOnceIfReady();
delay(5000);
}
}
void setup() {
Serial.begin(115200);
while (!Serial) { ; }
Serial.println("=== Program kalibracji SCD41 (Arduino MEGA) ===");
Wire.begin(); // MEGA: SDA=20, SCL=21
uint16_t err = scd4x.begin(Wire);
printErrorIfAny(err, "begin()");
if (err) {
Serial.println("Nie można zainicjalizować czujnika – sprawdź połączenia.");
while (1) delay(1000);
}
// Bezpiecznie zatrzymaj, pokaż numer seryjny i status ASC
stopMeasIfNeeded();
showSerialNumber();
bool asc = false;
err = scd4x.getAutomaticSelfCalibrationEnabled(asc);
if (!err) {
Serial.print("ASC: "); Serial.println(asc ? "WL" : "WYL");
}
// Start standardowych pomiarów
startMeasIfNeeded();
printMenu();
}
void loop() {
if (Serial.available()) {
char c = (char)Serial.read();
switch (c) {
case '1': doFRC(); break;
case '2': toggleASC(); break;
case '3': setAltitude(); break;
case '4': setAmbientPressure(); break;
case '5': setTempOffset(); break;
case '6': doFactoryReset(); break;
case '7': doSelfTest(); break;
case '8': readOnceIfReady(); break;
case '9': persist(); break;
case 's': startMeasIfNeeded(); break;
case 't': stopMeasIfNeeded(); break;
case 'm': printMenu(); break;
case 'q': continuousLoop(); break;
case '\n':
case '\r':
break;
default:
Serial.println("Nieznana komenda.");
break;
}
Serial.print("\nWybór: ");
}
}