Program Flow
Overview
The CICERONE AirLink firmware executes in two distinct phases: Setup (initialization) and Main Loop (continuous operation). This page details the execution flow and logic for both phases.
Setup Phase
Setup Phase Diagram
flowchart TD
n1["Start"] --> rtc_init
rtc_init["Initialize RTC and periodic alarms"] --> t6793_init["Initialize T6793 CO₂ sensor"]
t6793_init --> sen5x_init["Initialize SEN54 PM/VOC sensor"]
sen5x_init --> if_nbiot{"Enable NB-IoT?"}
if_nbiot -- Yes --> nbiot_init["Initialize NB-IoT module SIM7020G"]
if_nbiot -- No --> setup_done("Setup Complete")
nbiot_init --> setup_done
n1@{ shape: start}
class n1,setup_done final;
classDef default stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#000000
classDef final stroke-width:4px, stroke-dasharray: 0, stroke:#00C853, fill:#C8E6C9, color:#000000
Execution Sequence
The setup() function executes once at power-on or reset:
1. Serial Communication
#if DEBUG_LEVEL > 0
Serial.begin(115200);
while (!Serial) {
; // Wait for serial port to connect
}
DEBUG_INFO("CICERONE AirLink starting...");
#endif
Purpose: Initialize UART for debug output
Baud Rate: 115200
Conditional: Only if DEBUG_LEVEL > 0
2. RTC Initialization
Purpose: Configure DS3231M real-time clock
Tasks:
- Verify I2C communication
- Set initial time (if needed)
- Configure 10-minute alarm schedule
- Enable alarm interrupts
Critical: If RTC fails, system halts (time is essential)
3. T6793 CO₂ Sensor Initialization
Purpose: Initialize Telaire T6793-5K CO₂ sensor
Tasks:
- Configure I2C communication (address 0x15)
- Verify sensor response
- Set measurement mode
- Wait for sensor stabilization
Fallback: Continue if sensor fails (log error, skip readings)
4. SEN54 Sensor Initialization
Purpose: Initialize Sensirion SEN54 PM/VOC sensor
Tasks:
- Configure I2C communication (address 0x69)
- Soft reset sensor
- Start measurement mode
- Wait for warm-up period (~30 seconds for VOC accuracy)
Fallback: Continue if sensor fails (log error, skip readings)
5. NB-IoT Initialization (Conditional)
Purpose: Initialize SIM7020G NB-IoT module
Tasks:
- Configure UART communication (Serial1)
- Send AT commands to module
- Verify SIM card detection
- Register with network (may take 30-60 seconds)
- Configure APN settings
Conditional: Only if HABILITAR_NBIOT = 1
Fallback: Continue if fails (log error, local logging only)
Initialization Order Rationale
The specific order (RTC → T6793 → SEN54 → NB-IoT) ensures:
- Timing First: RTC must be functional before scheduling data collection
- Sensors Before Network: Sensors need warm-up time during network registration
- Critical to Optional: Essential components before optional features
- Fast to Slow: Quick initializations before time-consuming network setup
Typical Setup Duration
| Component | Duration | Notes |
|---|---|---|
| Serial | <100ms | Nearly instant |
| RTC | ~200ms | I2C communication + config |
| T6793 | ~500ms | Sensor wake-up |
| SEN54 | ~1-2s | Soft reset + measurement start |
| NB-IoT | 30-60s | Network registration varies |
| Total | ~35-65s | Mostly network registration |
Reducing Setup Time
During development, disable NB-IoT (HABILITAR_NBIOT = 0) to reduce setup time to ~3-5 seconds.
Main Loop Phase
Main Loop Diagram
flowchart TB
setup_done["Setup"] --> check_alarm_5s["Check if 5 seconds have elapsed"]
check_alarm_5s --> check_alarm_10min["Check if 10 minutes have elapsed"]
check_alarm_10min --> if_alarm_5s{"5-second flag active?"}
if_alarm_5s -- No --> if_alarm_10min{"10-minute flag active?"}
if_alarm_5s -- Yes --> accumulate_data["Read sensors and accumulate data"]
accumulate_data --> reset_alarm_5s["Reset 5-second flag"]
reset_alarm_5s --> if_alarm_10min
if_alarm_10min -- No --> check_alarm_5s
if_alarm_10min -- Yes --> average_data["Calculate average of accumulated readings"]
average_data --> if_nbiot2{"Enable NB-IoT?"}
if_nbiot2 -- No --> reset_alarm_10min
if_nbiot2 -- Yes --> transmit_data["Transmit averaged data via HTTP POST"]
transmit_data --> reset_alarm_10min["Reset 10-minute flag and counters"]
reset_alarm_10min --> check_alarm_5s
setup_done:::final
classDef default stroke-width:1px, stroke-dasharray:none, stroke:#374D7C, fill:#E2EBFF, color:#000000
classDef final stroke-width:4px, stroke-dasharray: 0, stroke:#00C853, fill:#C8E6C9, color:#000000
Main Loop Structure
The loop() function runs continuously with minimal blocking:
void loop() {
// Check timers
check_alarma_5s();
check_alarma_10min();
// Handle 5-second cycle
if (alarma_5s) {
acumular_datos();
alarma_5s = false;
}
// Handle 10-minute cycle
if (alarma_10min) {
promediar_datos();
#if HABILITAR_NBIOT
String json = nbiot_paquete();
nbiot_enviar(json);
#endif
alarma_10min = false;
}
}
5-Second Cycle (Data Acquisition)
void check_alarma_5s() {
unsigned long current_millis = millis();
if (current_millis - prev_millis_5s >= 5000) {
prev_millis_5s = current_millis;
alarma_5s = true;
}
}
Non-blocking: Function returns immediately
Accuracy: ±10ms (millis() resolution)
Overflow Handling: Subtraction math handles millis() rollover (every 49.7 days)
Data Accumulation
void acumular_datos() {
DEBUG_VERBOSE("Reading sensors...");
// Read SEN54 sensor
if (sen5x_leer()) {
sum_sen5x_mc_1p0 += sen5x_mc_1p0;
sum_sen5x_mc_2p5 += sen5x_mc_2p5;
sum_sen5x_mc_4p0 += sen5x_mc_4p0;
sum_sen5x_mc_10p0 += sen5x_mc_10p0;
sum_sen5x_voc_index += sen5x_voc_index;
sum_sen5x_temp += sen5x_temp;
sum_sen5x_hum += sen5x_hum;
cont1++;
}
// Read T6793 sensor
if (t6793_leer()) {
sum_t6793_co2 += t6793_co2;
cont2++;
}
DEBUG_VERBOSE("Samples: SEN54=%d, T6793=%d", cont1, cont2);
}
Accumulation Strategy
Separate Counters: cont1 (SEN54) and cont2 (T6793)
Rationale:
- Sensors may have different reliability
- Failed reads don't corrupt averages
- Allows for different sampling rates
Floating-Point Precision: All accumulation uses float to preserve decimal places
Error Handling: Failed sensor reads skip accumulation (counter not incremented)
Expected Sample Count
Over 10 minutes:
- Target: 120 samples per sensor (12/minute × 10 minutes)
- Typical: 118-120 samples (some reads may fail)
- Minimum Acceptable: 100 samples (if below, may indicate sensor issue)
10-Minute Cycle (Averaging & Transmission)
void check_alarma_10min() {
DateTime now = rtc.getRTCTime();
// Check if current minute is multiple of 10
if (now.minute() % 10 == 0 && now.second() < 5) {
if (!alarm_10min_triggered) {
alarma_10min = true;
alarm_10min_triggered = true;
}
} else {
alarm_10min_triggered = false; // Reset for next 10-minute mark
}
}
RTC-Based: Uses real-time clock, not millis()
Accuracy: ±1 second
Trigger Points: :00, :10, :20, :30, :40, :50 of each hour
Debounce: alarm_10min_triggered prevents multiple triggers
Data Averaging
void promediar_datos() {
DEBUG_INFO("Calculating averages...");
// Calculate SEN54 averages
if (cont1 > 0) {
avg_sen5x_mc_1p0 = sum_sen5x_mc_1p0 / cont1;
avg_sen5x_mc_2p5 = sum_sen5x_mc_2p5 / cont1;
avg_sen5x_mc_4p0 = sum_sen5x_mc_4p0 / cont1;
avg_sen5x_mc_10p0 = sum_sen5x_mc_10p0 / cont1;
avg_sen5x_voc_index = sum_sen5x_voc_index / cont1;
avg_sen5x_temp = sum_sen5x_temp / cont1;
avg_sen5x_hum = sum_sen5x_hum / cont1;
}
// Calculate T6793 averages
if (cont2 > 0) {
avg_t6793_co2 = sum_t6793_co2 / cont2;
}
// Get timestamp from RTC
DateTime now = rtc.getRTCTime();
fecha = formatDate(now);
hora = formatTime(now);
DEBUG_INFO("PM2.5: %.2f, CO2: %.0f", avg_sen5x_mc_2p5, avg_t6793_co2);
}
Averaging Algorithm
Method: Simple arithmetic mean
Formula: average = sum / count
Advantages:
- Memory-efficient (no array storage needed)
- Computationally simple
- Suitable for air quality data (outliers are rare)
Alternative Approaches (not implemented):
- Median filtering (requires storing all samples)
- Weighted average (more complex, minimal benefit)
- Outlier rejection (adds complexity, sensor failures already handled)
Reset Accumulators
void reset_accumulators() {
// Reset SEN54 sums
sum_sen5x_mc_1p0 = 0.0f;
sum_sen5x_mc_2p5 = 0.0f;
// ... (all other sums)
// Reset counters
cont1 = 0;
cont2 = 0;
}
When: After transmission (or averaging if NB-IoT disabled)
Purpose: Prepare for next 10-minute cycle
Critical: Must reset to avoid overflow and incorrect averages
Data Transmission
#if HABILITAR_NBIOT
String json = nbiot_paquete();
bool success = nbiot_enviar(json);
if (success) {
DEBUG_INFO("Data transmitted successfully");
} else {
DEBUG_ERROR("Transmission failed");
}
#endif
Conditional: Only if HABILITAR_NBIOT = 1
JSON Format: See Data Transmission
Retry Logic: Handled within nbiot_enviar()
Duration: 30-60 seconds typically
Timing Characteristics
5-Second Cycle
| Metric | Value | Notes |
|---|---|---|
| Interval | 5000ms | Configurable in code |
| Accuracy | ±10ms | Based on millis() |
| Duration | ~100-200ms | Sensor I2C reads |
| Blocking | Minimal | I2C reads are brief |
| CPU Usage | <5% | Most time idle |
10-Minute Cycle
| Metric | Value | Notes |
|---|---|---|
| Interval | 600s (10 min) | Based on RTC |
| Accuracy | ±1s | RTC precision |
| Duration | 30-60s | Mostly NB-IoT transmission |
| Blocking | Significant | AT commands wait for response |
| CPU Usage | ~80% | During transmission |
Daily Operation
- Data Points: 144 per day (1 every 10 minutes)
- Sensor Readings: 17,280 per day (144 × 120 samples)
- Transmissions: 144 per day (if NB-IoT enabled)
- Total Uptime: 24 hours (no sleep modes)
Error Handling During Operation
Sensor Read Failures
Strategy: Skip sample, continue operation
if (!sen5x_leer()) {
DEBUG_WARN("SEN5X read failed, skipping sample");
// Counter not incremented, sum not updated
return;
}
Impact: Slight reduction in sample count (e.g., 118 instead of 120)
Recovery: Automatically retries next 5-second cycle
RTC Failures
Strategy: Halt or use backup timing
if (!rtc.isRunning()) {
DEBUG_ERROR("RTC stopped, check battery");
// Option 1: Halt (safe but stops data collection)
while(1);
// Option 2: Fall back to millis() timing (less accurate)
use_millis_timing = true;
}
Critical: RTC failure affects timestamps (data integrity issue)
NB-IoT Transmission Failures
Strategy: Log error, continue to next cycle
if (!nbiot_enviar(json)) {
DEBUG_ERROR("Transmission failed, data lost");
// Data is lost (no local storage)
// Next 10-minute cycle will try again with new data
}
Impact: Data gap on server
Performance Monitoring
Debug Output Example
[INFO][5012][MAIN] Setup complete
[VERBO][10234][ALARMA] Reading sensors... (sample 1/120)
[VERBO][15234][ALARMA] Reading sensors... (sample 2/120)
...
[INFO][610234][ALARMA] Calculating averages... (120 samples)
[INFO][610235][ALARMA] PM2.5: 12.34, CO2: 687
[INFO][610250][NBIOT] Transmitting data...
[INFO][645123][NBIOT] Transmission successful (response: 200 OK)
Related Documentation
- Architecture - System architecture and modular design
- Data Transmission - Network communication details
- Sensor Interfaces - Sensor communication protocols
- Debug Configuration - Debug system setup