
จากช่วงต้นปี เกิดวิกฤตฝุ่นพิษ PM2.5 ทำให้ทางผมได้ทดลองใช้ เซ็นเซอร์วัดฝุ่น และ M5Stack ทำต้นแบบเครื่องวัดฝุ่นขึ้นมาง่ายๆ ตอนนี้ถึงเวลาทำให้สมบูรณ์แล้ว
- รวมโมดุล จับเอา Sensor และ Battery ทั้งหมดใส่เข้าไปในเคสเดียวกัน ด้วย 3D Printer ขึ้นรูปมา ดังภาพ โมดิฟาย M5Stack โมดุลเดิมนิดหน่อยครับ
- แก้ไข UI ใหม่ครับ ให้เป็นระเบียบมากขึ้น ช่วงแรกยังไม่คุ้นกับ M5Stack เลยทำให้แสดงผลได้ก่อน ในตอนนี้ทางผมปรับปรุง UI ใหม่ และแก้ไขเรื่องกระพริบ และ ผมคิดว่า Code ใหม่น่าจะช่วยให้คนที่สนใจ ต่อยอดงานอื่นๆ ได้ง่ายขึ้นครับ
#include <M5Stack.h>
#include <HardwareSerial.h>
//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)
#define GFXFF 1
#define FF9 &FreeSans9pt7b
#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24 &Satisfy_24
#define CF_Y32 &Yellowtail_32
struct pms7003data {
uint16_t framelen;
uint16_t pm10_standard, pm25_standard, pm100_standard;
uint16_t pm10_env, pm25_env, pm100_env;
uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
uint16_t unused;
uint16_t checksum;
};
struct displayCode {
int bg_color;
int text_color;
int AQI;
String Code;
};
struct pms7003data data;
struct displayCode displayCode_t;
void setup() {
M5.begin();
// our debugging output
Serial.begin(115200);
// sensor baud rate is 9600
pmsSerial.begin(9600);
M5.Lcd.setFreeFont(CF_RT24);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.fillScreen(TFT_BLACK);
M5.Lcd.setTextColor(TFT_WHITE);
M5.Lcd.drawString("LOADING DATA", M5.Lcd.width()/2, M5.Lcd.height()/2, GFXFF);
}
int old=-1;
void loop() {
if (readPMSdata(&pmsSerial)) {
displayCode_t = PM25AQI(data.pm25_env);
// update bg color
if (old != displayCode_t.bg_color) {
M5.Lcd.setFreeFont(CF_RT24); // Select the font
M5.Lcd.setTextSize(0.5);
M5.Lcd.fillScreen(displayCode_t.bg_color);
M5.Lcd.setTextColor(displayCode_t.text_color, displayCode_t.bg_color);
M5.Lcd.setTextDatum(ML_DATUM);
M5.Lcd.drawString("PM2.5(AQI)", 5, 15, GFXFF);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.drawString(displayCode_t.Code, M5.Lcd.width()/2, M5.Lcd.height()/2+40, GFXFF);// Print the string name of the font
M5.Lcd.setFreeFont(FF9);
M5.Lcd.setTextPadding(0);
M5.Lcd.drawString("PM1.0", 40, 195, GFXFF);
M5.Lcd.drawString("PM2.5", M5.Lcd.width()/2, 195, GFXFF);
M5.Lcd.drawString("PM10", 280, 195, GFXFF);
}
M5.Lcd.setFreeFont(FF9);
M5.Lcd.setTextPadding(40);
M5.Lcd.setTextSize(1);
M5.Lcd.drawNumber( data.pm10_env, 40, 220);
M5.Lcd.drawNumber( data.pm25_env, M5.Lcd.width()/2, 220);
M5.Lcd.drawNumber( data.pm100_env, 280, 220);
M5.Lcd.setFreeFont(CF_OL32);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextPadding(120);
M5.Lcd.drawNumber( displayCode_t.AQI, M5.Lcd.width()/2, M5.Lcd.height()/2-20);
printTest(); //debug
old = displayCode_t.bg_color;
}
}
displayCode PM25AQI(int reading) {
struct displayCode display_t;
display_t.text_color = TFT_WHITE;
if (reading <= 25) {
display_t.bg_color = TFT_BLUE;
display_t.Code = "GOOD";
display_t.AQI = reading;
} else if ( (reading >= 26) && (reading <= 37) ) {
display_t.bg_color = TFT_GREEN;
display_t.Code = "Moderate";
display_t.AQI = map(reading,26,37,26,50);
} else if ( (reading >= 38) && (reading <= 50) ) {
display_t.bg_color = TFT_GREENYELLOW;
display_t.Code = "unhealthy"; //unhealthy for kid
display_t.AQI = map(reading,38,50,51,100);
} else if ( (reading >= 51) && (reading <= 90) ) {
display_t.bg_color = TFT_ORANGE;
display_t.Code = "very unhealthy"; //very unhealthy
display_t.AQI = map( reading,51,90,101,200 );
} else if (data.pm25_env >= 91) {
display_t.bg_color = TFT_RED;
display_t.AQI = map( reading,91,200,201,510 );
display_t.Code = "Hazardous"; //Hazardous
}
return display_t;
}
void printTest() {
// reading data was successful!
Serial.println();
Serial.println("---------------------------------------");
Serial.println("Concentration Units (standard)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
Serial.println("---------------------------------------");
Serial.println("Concentration Units (environmental)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
Serial.println("---------------------------------------");
Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
Serial.println("---------------------------------------");
}
boolean readPMSdata(Stream *s) {
if (! s->available()) {
return false;
}
// Read a byte at a time until we get to the special '0x42' start-byte
if (s->peek() != 0x42) {
s->read();
return false;
}
// Now read all 32 bytes
if (s->available() < 32) {
return false;
}
uint8_t buffer[32];
uint16_t sum = 0;
s->readBytes(buffer, 32);
// get checksum ready
for (uint8_t i=0; i<30; i++) {
sum += buffer[i];
}
/* debugging
for (uint8_t i=2; i<32; i++) {
Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
}
Serial.println();
*/
// The data comes in endian'd, this solves it so it works on all platforms
uint16_t buffer_u16[15];
for (uint8_t i=0; i<15; i++) {
buffer_u16[i] = buffer[2 + i*2 + 1];
buffer_u16[i] += (buffer[2 + i*2] << 8);
}
// put it into a nice struct :)
memcpy((void *)&data, (void *)buffer_u16, 30);
if (sum != data.checksum) {
Serial.println("Checksum failure");
return false;
}
// success!
return true;
}
#include <M5Stack.h>
#include <HardwareSerial.h>
//const uint8_t PMS_RX=16, PMS_TX=17;
HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX)
#define GFXFF 1
#define FF9 &FreeSans9pt7b
#define CF_OL24 &Orbitron_Light_24
#define CF_OL32 &Orbitron_Light_32
#define CF_RT24 &Roboto_Thin_24
#define CF_S24 &Satisfy_24
#define CF_Y32 &Yellowtail_32
struct pms7003data {
uint16_t framelen;
uint16_t pm10_standard, pm25_standard, pm100_standard;
uint16_t pm10_env, pm25_env, pm100_env;
uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um;
uint16_t unused;
uint16_t checksum;
};
struct displayCode {
int bg_color;
int text_color;
int AQI;
String Code;
};
struct pms7003data data;
struct displayCode displayCode_t;
void setup() {
M5.begin();
// our debugging output
Serial.begin(115200);
// sensor baud rate is 9600
pmsSerial.begin(9600);
M5.Lcd.setFreeFont(CF_RT24);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.fillScreen(TFT_BLACK);
M5.Lcd.setTextColor(TFT_WHITE);
M5.Lcd.drawString("LOADING DATA", M5.Lcd.width()/2, M5.Lcd.height()/2, GFXFF);
}
int old=-1;
void loop() {
if (readPMSdata(&pmsSerial)) {
displayCode_t = PM25AQI(data.pm25_env);
// update bg color
if (old != displayCode_t.bg_color) {
M5.Lcd.setFreeFont(CF_RT24); // Select the font
M5.Lcd.setTextSize(0.5);
M5.Lcd.fillScreen(displayCode_t.bg_color);
M5.Lcd.setTextColor(displayCode_t.text_color, displayCode_t.bg_color);
M5.Lcd.setTextDatum(ML_DATUM);
M5.Lcd.drawString("PM2.5(AQI)", 5, 15, GFXFF);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.drawString(displayCode_t.Code, M5.Lcd.width()/2, M5.Lcd.height()/2+40, GFXFF);// Print the string name of the font
M5.Lcd.setFreeFont(FF9);
M5.Lcd.setTextPadding(0);
M5.Lcd.drawString("PM1.0", 40, 195, GFXFF);
M5.Lcd.drawString("PM2.5", M5.Lcd.width()/2, 195, GFXFF);
M5.Lcd.drawString("PM10", 280, 195, GFXFF);
}
M5.Lcd.setFreeFont(FF9);
M5.Lcd.setTextPadding(40);
M5.Lcd.setTextSize(1);
M5.Lcd.drawNumber( data.pm10_env, 40, 220);
M5.Lcd.drawNumber( data.pm25_env, M5.Lcd.width()/2, 220);
M5.Lcd.drawNumber( data.pm100_env, 280, 220);
M5.Lcd.setFreeFont(CF_OL32);
M5.Lcd.setTextDatum(MC_DATUM);
M5.Lcd.setTextSize(2);
M5.Lcd.setTextPadding(120);
M5.Lcd.drawNumber( displayCode_t.AQI, M5.Lcd.width()/2, M5.Lcd.height()/2-20);
printTest(); //debug
old = displayCode_t.bg_color;
}
}
displayCode PM25AQI(int reading) {
struct displayCode display_t;
display_t.text_color = TFT_WHITE;
if (reading <= 25) {
display_t.bg_color = TFT_BLUE;
display_t.Code = "GOOD";
display_t.AQI = reading;
} else if ( (reading >= 26) && (reading <= 37) ) {
display_t.bg_color = TFT_GREEN;
display_t.Code = "Moderate";
display_t.AQI = map(reading,26,37,26,50);
} else if ( (reading >= 38) && (reading <= 50) ) {
display_t.bg_color = TFT_GREENYELLOW;
display_t.Code = "unhealthy"; //unhealthy for kid
display_t.AQI = map(reading,38,50,51,100);
} else if ( (reading >= 51) && (reading <= 90) ) {
display_t.bg_color = TFT_ORANGE;
display_t.Code = "very unhealthy"; //very unhealthy
display_t.AQI = map( reading,51,90,101,200 );
} else if (data.pm25_env >= 91) {
display_t.bg_color = TFT_RED;
display_t.AQI = map( reading,91,200,201,510 );
display_t.Code = "Hazardous"; //Hazardous
}
return display_t;
}
void printTest() {
// reading data was successful!
Serial.println();
Serial.println("---------------------------------------");
Serial.println("Concentration Units (standard)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_standard);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard);
Serial.println("---------------------------------------");
Serial.println("Concentration Units (environmental)");
Serial.print("PM 1.0: "); Serial.print(data.pm10_env);
Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env);
Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env);
Serial.println("---------------------------------------");
Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um);
Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um);
Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um);
Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um);
Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um);
Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um);
Serial.println("---------------------------------------");
}
boolean readPMSdata(Stream *s) {
if (! s->available()) {
return false;
}
// Read a byte at a time until we get to the special '0x42' start-byte
if (s->peek() != 0x42) {
s->read();
return false;
}
// Now read all 32 bytes
if (s->available() < 32) {
return false;
}
uint8_t buffer[32];
uint16_t sum = 0;
s->readBytes(buffer, 32);
// get checksum ready
for (uint8_t i=0; i<30; i++) {
sum += buffer[i];
}
/* debugging
for (uint8_t i=2; i<32; i++) {
Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", ");
}
Serial.println();
*/
// The data comes in endian'd, this solves it so it works on all platforms
uint16_t buffer_u16[15];
for (uint8_t i=0; i<15; i++) {
buffer_u16[i] = buffer[2 + i*2 + 1];
buffer_u16[i] += (buffer[2 + i*2] << 8);
}
// put it into a nice struct :)
memcpy((void *)&data, (void *)buffer_u16, 30);
if (sum != data.checksum) {
Serial.println("Checksum failure");
return false;
}
// success!
return true;
}
#include <M5Stack.h> #include <HardwareSerial.h> //const uint8_t PMS_RX=16, PMS_TX=17; HardwareSerial pmsSerial(2); // UART2 on GPIO16(RX),GPIO17(TX) #define GFXFF 1 #define FF9 &FreeSans9pt7b #define CF_OL24 &Orbitron_Light_24 #define CF_OL32 &Orbitron_Light_32 #define CF_RT24 &Roboto_Thin_24 #define CF_S24 &Satisfy_24 #define CF_Y32 &Yellowtail_32 struct pms7003data { uint16_t framelen; uint16_t pm10_standard, pm25_standard, pm100_standard; uint16_t pm10_env, pm25_env, pm100_env; uint16_t particles_03um, particles_05um, particles_10um, particles_25um, particles_50um, particles_100um; uint16_t unused; uint16_t checksum; }; struct displayCode { int bg_color; int text_color; int AQI; String Code; }; struct pms7003data data; struct displayCode displayCode_t; void setup() { M5.begin(); // our debugging output Serial.begin(115200); // sensor baud rate is 9600 pmsSerial.begin(9600); M5.Lcd.setFreeFont(CF_RT24); M5.Lcd.setTextDatum(MC_DATUM); M5.Lcd.fillScreen(TFT_BLACK); M5.Lcd.setTextColor(TFT_WHITE); M5.Lcd.drawString("LOADING DATA", M5.Lcd.width()/2, M5.Lcd.height()/2, GFXFF); } int old=-1; void loop() { if (readPMSdata(&pmsSerial)) { displayCode_t = PM25AQI(data.pm25_env); // update bg color if (old != displayCode_t.bg_color) { M5.Lcd.setFreeFont(CF_RT24); // Select the font M5.Lcd.setTextSize(0.5); M5.Lcd.fillScreen(displayCode_t.bg_color); M5.Lcd.setTextColor(displayCode_t.text_color, displayCode_t.bg_color); M5.Lcd.setTextDatum(ML_DATUM); M5.Lcd.drawString("PM2.5(AQI)", 5, 15, GFXFF); M5.Lcd.setTextDatum(MC_DATUM); M5.Lcd.drawString(displayCode_t.Code, M5.Lcd.width()/2, M5.Lcd.height()/2+40, GFXFF);// Print the string name of the font M5.Lcd.setFreeFont(FF9); M5.Lcd.setTextPadding(0); M5.Lcd.drawString("PM1.0", 40, 195, GFXFF); M5.Lcd.drawString("PM2.5", M5.Lcd.width()/2, 195, GFXFF); M5.Lcd.drawString("PM10", 280, 195, GFXFF); } M5.Lcd.setFreeFont(FF9); M5.Lcd.setTextPadding(40); M5.Lcd.setTextSize(1); M5.Lcd.drawNumber( data.pm10_env, 40, 220); M5.Lcd.drawNumber( data.pm25_env, M5.Lcd.width()/2, 220); M5.Lcd.drawNumber( data.pm100_env, 280, 220); M5.Lcd.setFreeFont(CF_OL32); M5.Lcd.setTextDatum(MC_DATUM); M5.Lcd.setTextSize(2); M5.Lcd.setTextPadding(120); M5.Lcd.drawNumber( displayCode_t.AQI, M5.Lcd.width()/2, M5.Lcd.height()/2-20); printTest(); //debug old = displayCode_t.bg_color; } } displayCode PM25AQI(int reading) { struct displayCode display_t; display_t.text_color = TFT_WHITE; if (reading <= 25) { display_t.bg_color = TFT_BLUE; display_t.Code = "GOOD"; display_t.AQI = reading; } else if ( (reading >= 26) && (reading <= 37) ) { display_t.bg_color = TFT_GREEN; display_t.Code = "Moderate"; display_t.AQI = map(reading,26,37,26,50); } else if ( (reading >= 38) && (reading <= 50) ) { display_t.bg_color = TFT_GREENYELLOW; display_t.Code = "unhealthy"; //unhealthy for kid display_t.AQI = map(reading,38,50,51,100); } else if ( (reading >= 51) && (reading <= 90) ) { display_t.bg_color = TFT_ORANGE; display_t.Code = "very unhealthy"; //very unhealthy display_t.AQI = map( reading,51,90,101,200 ); } else if (data.pm25_env >= 91) { display_t.bg_color = TFT_RED; display_t.AQI = map( reading,91,200,201,510 ); display_t.Code = "Hazardous"; //Hazardous } return display_t; } void printTest() { // reading data was successful! Serial.println(); Serial.println("---------------------------------------"); Serial.println("Concentration Units (standard)"); Serial.print("PM 1.0: "); Serial.print(data.pm10_standard); Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_standard); Serial.print("\t\tPM 10: "); Serial.println(data.pm100_standard); Serial.println("---------------------------------------"); Serial.println("Concentration Units (environmental)"); Serial.print("PM 1.0: "); Serial.print(data.pm10_env); Serial.print("\t\tPM 2.5: "); Serial.print(data.pm25_env); Serial.print("\t\tPM 10: "); Serial.println(data.pm100_env); Serial.println("---------------------------------------"); Serial.print("Particles > 0.3um / 0.1L air:"); Serial.println(data.particles_03um); Serial.print("Particles > 0.5um / 0.1L air:"); Serial.println(data.particles_05um); Serial.print("Particles > 1.0um / 0.1L air:"); Serial.println(data.particles_10um); Serial.print("Particles > 2.5um / 0.1L air:"); Serial.println(data.particles_25um); Serial.print("Particles > 5.0um / 0.1L air:"); Serial.println(data.particles_50um); Serial.print("Particles > 10.0 um / 0.1L air:"); Serial.println(data.particles_100um); Serial.println("---------------------------------------"); } boolean readPMSdata(Stream *s) { if (! s->available()) { return false; } // Read a byte at a time until we get to the special '0x42' start-byte if (s->peek() != 0x42) { s->read(); return false; } // Now read all 32 bytes if (s->available() < 32) { return false; } uint8_t buffer[32]; uint16_t sum = 0; s->readBytes(buffer, 32); // get checksum ready for (uint8_t i=0; i<30; i++) { sum += buffer[i]; } /* debugging for (uint8_t i=2; i<32; i++) { Serial.print("0x"); Serial.print(buffer[i], HEX); Serial.print(", "); } Serial.println(); */ // The data comes in endian'd, this solves it so it works on all platforms uint16_t buffer_u16[15]; for (uint8_t i=0; i<15; i++) { buffer_u16[i] = buffer[2 + i*2 + 1]; buffer_u16[i] += (buffer[2 + i*2] << 8); } // put it into a nice struct :) memcpy((void *)&data, (void *)buffer_u16, 30); if (sum != data.checksum) { Serial.println("Checksum failure"); return false; } // success! return true; }
เพิ่มเติมตอนนี้ มีน้องใหม่ สำหรับ Node32Lite แสดงผลได้เหมือนกัน แค่ต้องเหนื่อยทำเองครับ เดี่ยวจะแชร์ กันในวันหลัง