Skip to content

Commit 1fe51a7

Browse files
committed
WIP: uart logging
1 parent bab3c65 commit 1fe51a7

File tree

3 files changed

+382
-31
lines changed

3 files changed

+382
-31
lines changed

platformio.ini

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
9999
; platform = https://github.com/pioarduino/platform-espressif32#develop
100100
lib_ignore = WiFiNINA, WiFi101, OneWire
101101
monitor_filters = esp32_exception_decoder, time
102+
debug_tool = esp-builtin
103+
debug_init_break = tbreak ws_uart_drv_pm25aqi
102104

103105
; Common build environment for ESP8266 platform
104106
[common:esp8266]
@@ -220,7 +222,7 @@ extends = common:esp32
220222
board = adafruit_feather_esp32s3
221223
build_flags = -DARDUINO_ADAFRUIT_FEATHER_ESP32S3 -DBOARD_HAS_PSRAM
222224
;set partition to tinyuf2-partitions-4MB.csv as of idf 5.1
223-
board_build.partitions = tinyuf2-partitions-4MB.csv
225+
board_build.partitions = tinyuf2-partitions-4MB-noota.csv
224226
extra_scripts = pre:rename_usb_config.py
225227

226228
; Adafruit Feather ESP32-S3 NO PSRAM

pm25.cpp.txt

Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
/*!
2+
* @file Adafruit_PM25AQI.cpp
3+
*
4+
* @mainpage Adafruit PM2.5 air quality sensor driver
5+
*
6+
* @section intro_sec Introduction
7+
*
8+
* This is the documentation for Adafruit's PM2.5 AQI driver for the
9+
* Arduino platform. It is designed specifically to work with the
10+
* Adafruit PM2.5 Air quality sensors: http://www.adafruit.com/products/4632
11+
*
12+
* This library also works with the Cubic PM1006 UART Air Quality Sensor.
13+
*
14+
* These sensors use I2C or UART to communicate.
15+
*
16+
* Adafruit invests time and resources providing this open source code,
17+
* please support Adafruit and open-source hardware by purchasing
18+
* products from Adafruit!
19+
*
20+
*
21+
* @section author Author
22+
* Written by Ladyada for Adafruit Industries.
23+
* Modified by Brent Rubell for Adafruit Industries for use with Cubic PM1006
24+
* Air Quality Sensor.
25+
*
26+
* @section license License
27+
* BSD license, all text here must be included in any redistribution.
28+
*
29+
*/
30+
// #include <Wippersnapper.h>
31+
#include "Adafruit_PM25AQI.h"
32+
#include <math.h>
33+
34+
/*!
35+
* @brief Instantiates a new PM25AQI class
36+
*/
37+
Adafruit_PM25AQI::Adafruit_PM25AQI() {}
38+
39+
/*!
40+
* @brief Setups the hardware and detects a valid PMSA003I. Initializes I2C.
41+
* @param theWire
42+
* Optional pointer to I2C interface, otherwise use Wire
43+
* @return True if PMSA003I found on I2C, False if something went wrong!
44+
*/
45+
bool Adafruit_PM25AQI::begin_I2C(TwoWire *theWire) {
46+
if (!i2c_dev) {
47+
i2c_dev = new Adafruit_I2CDevice(PMSA003I_I2CADDR_DEFAULT, theWire);
48+
}
49+
50+
if (!i2c_dev->begin()) {
51+
return false;
52+
}
53+
54+
return true;
55+
}
56+
57+
/*!
58+
* @brief Setups the hardware and detects a valid UART PM2.5
59+
* @param theSerial
60+
* Pointer to Stream (HardwareSerial/SoftwareSerial) interface
61+
* @return True
62+
*/
63+
bool Adafruit_PM25AQI::begin_UART(Stream *theSerial) {
64+
serial_dev = theSerial;
65+
66+
return true;
67+
}
68+
69+
/*!
70+
* @brief Setups the hardware and detects a valid UART PM2.5
71+
* @param data
72+
* Pointer to PM25_AQI_Data that will be filled by read()ing
73+
* @return True on successful read, false if timed out or bad data
74+
*/
75+
bool Adafruit_PM25AQI::read(PM25_AQI_Data *data) {
76+
uint8_t buffer[32];
77+
size_t bufLen = sizeof(buffer);
78+
uint16_t sum = 0;
79+
uint8_t csum = 0;
80+
bool is_pm1006 = false;
81+
82+
if (!data) {
83+
return false;
84+
}
85+
86+
if (i2c_dev) { // ok using i2c?
87+
if (!i2c_dev->read(buffer, 32)) {
88+
return false;
89+
}
90+
} else if (serial_dev) { // ok using uart
91+
if (!serial_dev->available()) {
92+
Serial.println("PM25: Serial data unavailable");
93+
return false;
94+
}
95+
96+
int skipped = 0;
97+
while ((skipped < 32) && (serial_dev->peek() != 0x42) &&
98+
(serial_dev->peek() != 0x16)) {
99+
serial_dev->read();
100+
skipped++;
101+
if (!serial_dev->available()) {
102+
Serial.println("PM25: Serial data unavailable part way through");
103+
return false;
104+
}
105+
}
106+
107+
// Check for the start character in the stream for both sensors
108+
if ((serial_dev->peek() != 0x42) && (serial_dev->peek() != 0x16)) {
109+
Serial.println("PM25: Serial peek failed");
110+
serial_dev->read();
111+
return false;
112+
}
113+
114+
// Are we using the Cubic PM1006 sensor?
115+
if (serial_dev->peek() == 0x16) {
116+
is_pm1006 = true; // Set flag to indicate we are using the PM1006
117+
bufLen =
118+
20; // Reduce buffer read length to 20 bytes. Last 12 bytes ignored.
119+
}
120+
121+
// Are there enough bytes to read from?
122+
if (serial_dev->available() < bufLen) {
123+
Serial.println("PM25: Serial data too short");
124+
return false;
125+
}
126+
127+
// Read all available bytes from the serial stream
128+
serial_dev->readBytes(buffer, bufLen);
129+
} else {
130+
return false;
131+
}
132+
Serial.println("PM25: Serial data fetch done");
133+
134+
// Validate start byte is correct if using Adafruit PM sensors
135+
if ((!is_pm1006 && (buffer[0] != 0x42 || buffer[1] != 0x4d))) {
136+
Serial.println("PM25: Serial data start incorrect (not pm1006)");
137+
138+
return false;
139+
}
140+
141+
// Validate start header is correct if using Cubic PM1006 sensor
142+
if (is_pm1006 &&
143+
(buffer[0] != 0x16 || buffer[1] != 0x11 || buffer[2] != 0x0B)) {
144+
Serial.println("PM25: Serial data start incorrect (pm1006)");
145+
146+
return false;
147+
}
148+
149+
// Calculate checksum
150+
if (!is_pm1006) {
151+
for (uint8_t i = 0; i < 30; i++) {
152+
sum += buffer[i];
153+
}
154+
} else {
155+
for (uint8_t i = 0; i < bufLen; i++) {
156+
csum += buffer[i];
157+
}
158+
}
159+
160+
// Since header and checksum are OK, parse data from the buffer
161+
if (!is_pm1006) {
162+
// The data comes in endian'd, this solves it so it works on all platforms
163+
uint16_t buffer_u16[15];
164+
for (uint8_t i = 0; i < 15; i++) {
165+
buffer_u16[i] = buffer[2 + i * 2 + 1];
166+
buffer_u16[i] += (buffer[2 + i * 2] << 8);
167+
}
168+
// put it into a nice struct :)
169+
memcpy((void *)data, (void *)buffer_u16, 30);
170+
} else {
171+
// Cubic PM1006 sensor only produces a pm25_env reading
172+
data->pm25_env = (buffer[5] << 8) | buffer[6];
173+
data->checksum = sum;
174+
}
175+
176+
// Validate checksum
177+
if ((is_pm1006 && csum != 0) || (!is_pm1006 && sum != data->checksum)) {
178+
Serial.println("PM25: Serial data checksum incorrect");
179+
return false;
180+
}
181+
182+
Serial.println("PM25: Serial data checks complete, about to calculate AQIs");
183+
184+
// convert concentration to AQI
185+
data->aqi_pm25_us = pm25_aqi_us(data->pm25_env);
186+
data->aqi_pm25_china = pm25_aqi_china(data->pm25_env);
187+
data->aqi_pm100_us = pm100_aqi_us(data->pm100_env);
188+
data->aqi_pm100_china = pm100_aqi_china(data->pm100_env);
189+
Serial.println("PM25: Calculated AQIs, returning TRUE for read()");
190+
191+
// success!
192+
return true;
193+
}
194+
195+
/*!
196+
* @brief Get AQI of PM2.5 in US standard
197+
* @param concentration
198+
* the environmental concentration of pm2.5 in ug/m3
199+
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
200+
*/
201+
uint16_t Adafruit_PM25AQI::pm25_aqi_us(float concentration) {
202+
float c;
203+
float AQI;
204+
c = (floor(10 * concentration)) / 10;
205+
if (c < 0)
206+
AQI = 0;
207+
else if (c >= 0 && c < 12.1f) {
208+
AQI = linear(50, 0, 12, 0, c);
209+
} else if (c >= 12.1f && c < 35.5f) {
210+
AQI = linear(100, 51, 35.4f, 12.1f, c);
211+
} else if (c >= 35.5f && c < 55.5f) {
212+
AQI = linear(150, 101, 55.4f, 35.5f, c);
213+
} else if (c >= 55.5f && c < 150.5f) {
214+
AQI = linear(200, 151, 150.4f, 55.5f, c);
215+
} else if (c >= 150.5f && c < 250.5f) {
216+
AQI = linear(300, 201, 250.4f, 150.5f, c);
217+
} else if (c >= 250.5f && c < 350.5f) {
218+
AQI = linear(400, 301, 350.4f, 250.5f, c);
219+
} else if (c >= 350.5f && c < 500.5f) {
220+
AQI = linear(500, 401, 500.4f, 350.5f, c);
221+
} else {
222+
AQI = 99999; //
223+
}
224+
return round(AQI);
225+
}
226+
227+
/*!
228+
* @brief Get AQI of PM10 in US standard
229+
* @param concentration
230+
* the environmental concentration of pm10 in ug/m3
231+
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
232+
*/
233+
uint16_t Adafruit_PM25AQI::pm100_aqi_us(float concentration) {
234+
float c;
235+
float AQI;
236+
c = concentration;
237+
if (c < 0)
238+
AQI = 0;
239+
else if (c < 55) {
240+
AQI = linear(50, 0, 55, 0, c);
241+
} else if (c < 155) {
242+
AQI = linear(100, 51, 155, 55, c);
243+
} else if (c < 255) {
244+
AQI = linear(150, 101, 255, 155, c);
245+
} else if (c < 355) {
246+
AQI = linear(200, 151, 355, 255, c);
247+
} else if (c < 425) {
248+
AQI = linear(300, 201, 425, 355, c);
249+
} else if (c < 505) {
250+
AQI = linear(400, 301, 505, 425, c);
251+
} else if (c < 605) {
252+
AQI = linear(500, 401, 605, 505, c);
253+
} else {
254+
AQI = 99999; //
255+
}
256+
return round(AQI);
257+
}
258+
259+
/*!
260+
* @brief Get AQI of PM2.5 in China standard
261+
* @param concentration
262+
* the environmental concentration of pm2.5 in ug/m3
263+
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
264+
*/
265+
uint16_t Adafruit_PM25AQI::pm25_aqi_china(float concentration) {
266+
float c;
267+
float AQI;
268+
c = concentration;
269+
if (c < 0)
270+
AQI = 0;
271+
else if (c <= 35) {
272+
AQI = linear(50, 0, 35, 0, c);
273+
} else if (c <= 75) {
274+
AQI = linear(100, 51, 75, 35, c);
275+
} else if (c <= 115) {
276+
AQI = linear(150, 101, 115, 75, c);
277+
} else if (c <= 150) {
278+
AQI = linear(200, 151, 150, 115, c);
279+
} else if (c <= 250) {
280+
AQI = linear(300, 201, 250, 150, c);
281+
} else if (c <= 350) {
282+
AQI = linear(400, 301, 350, 250, c);
283+
} else if (c <= 500) {
284+
AQI = linear(500, 401, 500, 350, c);
285+
} else {
286+
AQI = 99999; //
287+
}
288+
return round(AQI);
289+
}
290+
291+
/*!
292+
* @brief Get AQI of PM10 in China standard
293+
* @param concentration
294+
* the environmental concentration of pm10 in ug/m3
295+
* @return AQI number. 0 to 500 for valid calculation. 99999 for out of range.
296+
*/
297+
uint16_t Adafruit_PM25AQI::pm100_aqi_china(float concentration) {
298+
float c;
299+
float AQI;
300+
c = concentration;
301+
if (c < 0)
302+
AQI = 0;
303+
else if (c <= 50) {
304+
AQI = linear(50, 0, 50, 0, c);
305+
} else if (c <= 150) {
306+
AQI = linear(100, 51, 150, 50, c);
307+
} else if (c <= 250) {
308+
AQI = linear(150, 101, 250, 150, c);
309+
} else if (c <= 350) {
310+
AQI = linear(200, 151, 350, 250, c);
311+
} else if (c <= 420) {
312+
AQI = linear(300, 201, 420, 350, c);
313+
} else if (c <= 500) {
314+
AQI = linear(400, 301, 500, 420, c);
315+
} else if (c <= 600) {
316+
AQI = linear(500, 401, 600, 500, c);
317+
} else {
318+
AQI = 99999; //
319+
}
320+
return round(AQI);
321+
}
322+
323+
/*!
324+
* @brief Linearly map a concentration value to its AQI level
325+
* @param aqi_high max aqi of the calculating range
326+
* @param aqi_low min aqi of the calculating range
327+
* @param conc_high max concentration value (ug/m3) of the calculating range
328+
* @param conc_low min concentration value (ug/m3) of the calculating range
329+
* @param concentration
330+
* the concentration value to be calculated
331+
* @return Calculated AQI value
332+
*/
333+
float Adafruit_PM25AQI::linear(uint16_t aqi_high, uint16_t aqi_low,
334+
float conc_high, float conc_low,
335+
float concentration) {
336+
float f;
337+
f = ((concentration - conc_low) / (conc_high - conc_low)) *
338+
(aqi_high - aqi_low) +
339+
aqi_low;
340+
return f;
341+
}

0 commit comments

Comments
 (0)