1+ /*
2+ Using the BMV080 Particulate Matter PM2.5 Sensor
3+
4+ This example shows how display the PM1 and Pm2.5 readings on a SparkFun Qwiic
5+ OLED Display, 1.3" wide.
6+
7+ It uses the sensor in "continuous mode" to get
8+ particulate matter readings once every second.
9+
10+ It uses polling of the device to check if new data is available.
11+
12+ By: Pete Lewis
13+ SparkFun Electronics
14+ Date: September, 2024
15+ SparkFun code, firmware, and software is released under the MIT License.
16+ Please see LICENSE.md for further details.
17+
18+ Hardware Connections:
19+ IoT RedBoard --> QWIIC
20+ QWIIC --> BMV080
21+ QWIIC --> QWIIC OLED Display
22+
23+ BMV080 "mode" jumper set to I2C (default)
24+
25+ Serial.print it out at 115200 baud to serial monitor.
26+
27+ Feel like supporting our work? Buy a board from SparkFun!
28+ https://www.sparkfun.com/products/26554
29+ */
30+
31+ #define StatLedPin 13
32+
33+ // BMV080 Specifics
34+ #include " SparkFun_BMV080_Arduino_Library.h" // CTRL+Click here to get the library: http://librarymanager/All#SparkFun_BMV080
35+ #include < Wire.h>
36+
37+ SparkFunBMV080I2C bmv080; // Create an instance of the BMV080 class
38+ #define BMV080_ADDR 0x57 // SparkFun BMV080 Breakout defaults to 0x57
39+
40+ float pm1Value = 0.0 ; // PM1 value - global so we can update it in the loop and
41+ float pm25Value = 0.0 ; // PM2.5 value - global so we can update it in the loop
42+
43+ // OLED Specifics
44+ #include < SparkFun_Qwiic_OLED.h> // http://librarymanager/All#SparkFun_Qwiic_OLED
45+
46+ // The Library supports four different types of SparkFun boards. The demo uses the following
47+ // defines to determine which device is being used. Uncomment the device being used for this demo.
48+
49+ // QwiicMicroOLED myOLED;
50+ // QwiicTransparentOLED myOLED;
51+ // QwiicNarrowOLED myOLED;
52+ Qwiic1in3OLED myOLED;
53+
54+ #include " res/qw_bmp_sparkfun.h"
55+
56+ // Fonts
57+ #include < res/qw_fnt_5x7.h>
58+ #include < res/qw_fnt_8x16.h>
59+ #include < res/qw_fnt_31x48.h>
60+ #include < res/qw_fnt_7segment.h>
61+ #include < res/qw_fnt_largenum.h>
62+
63+ // An array of fonts to loop over
64+ QwiicFont *demoFonts[] = {
65+ &QW_FONT_5X7,
66+ &QW_FONT_8X16,
67+ &QW_FONT_31X48,
68+ &QW_FONT_LARGENUM,
69+ &QW_FONT_7SEGMENT};
70+ int nFONTS = sizeof (demoFonts) / sizeof (demoFonts[0 ]);
71+ int iFont = 0 ;
72+
73+ // Some vars for the title.
74+ String strTitle = " <<Font>>" ;
75+ QwiicFont *pFntTitle = &QW_FONT_5X7;
76+
77+ QwiicFont *pFntLabel = &QW_FONT_5X7;
78+ QwiicFont *pFntValue = &QW_FONT_LARGENUM;
79+
80+ int width;
81+ int height;
82+
83+ // x position of the PM2.5 label, this will be set in the
84+ // writeStaticDisplayItems() function
85+ int xPosPM25;
86+
87+ // Some Dev boards have their QWIIC connector on Wire or Wire1
88+ // This #ifdef will help this sketch work across more products
89+
90+ #ifdef ARDUINO_SPARKFUN_THINGPLUS_RP2040
91+ #define wirePort Wire1
92+ #else
93+ #define wirePort Wire
94+ #endif
95+
96+ void setup ()
97+ {
98+ pinMode (StatLedPin, OUTPUT);
99+ digitalWrite (StatLedPin, LOW);
100+
101+ Serial.begin (115200 );
102+
103+ while (!Serial)
104+ delay (10 ); // Wait for Serial to become available.
105+ // Necessary for boards with native USB (like the SAMD51 Thing+).
106+ // For a final version of a project that does not need serial debug (or a USB cable plugged in),
107+ // Comment out this while loop, or it will prevent the remaining code from running.
108+
109+ Serial.println ();
110+ Serial.println (" BMV080 Example 8 - OLED Display" );
111+
112+ wirePort.begin ();
113+
114+ // Initalize the OLED device and related graphics system
115+ if (myOLED.begin (wirePort) == false )
116+ {
117+ Serial.println (" OLED Device begin failed. Freezing..." );
118+ writeCenteredStringToDisplay (" OLED Failure" );
119+ while (true )
120+ ;
121+ }
122+ Serial.println (" OLED Begin success" );
123+
124+ // save device dims for the test routines
125+ width = myOLED.getWidth ();
126+ height = myOLED.getHeight ();
127+
128+ showSplash ();
129+
130+ myOLED.setFont (demoFonts[1 ]);
131+
132+ if (bmv080.begin (BMV080_ADDR, wirePort) == false )
133+ {
134+ Serial.println (
135+ " BMV080 not detected at default I2C address. Check your jumpers and the hookup guide. Freezing..." );
136+ writeCenteredStringToDisplay (" BMV080 Failure" );
137+ while (1 )
138+ ;
139+ }
140+ Serial.println (" BMV080 found!" );
141+ writeCenteredStringToDisplay (" BMV080 Found" );
142+
143+ // wirePort.setClock(400000); //Increase I2C data rate to 400kHz
144+
145+ /* Initialize the Sensor (read driver, open, reset, id etc.)*/
146+ bmv080.init ();
147+
148+ /* Set the sensor mode to continuous mode */
149+ if (bmv080.setMode (SFE_BMV080_MODE_CONTINUOUS) == true )
150+ {
151+ Serial.println (" BMV080 set to continuous mode" );
152+ writeCenteredStringToDisplay (" Continuous Mode Set" );
153+ }
154+ else
155+ {
156+ Serial.println (" Error setting BMV080 mode" );
157+ writeCenteredStringToDisplay (" BMV080 Mode Error" );
158+ }
159+ }
160+
161+ void loop ()
162+ {
163+ if (bmv080.dataAvailable ())
164+ {
165+ pm25Value = bmv080.getPM25 ();
166+ pm1Value = bmv080.getPM1 ();
167+
168+ if (bmv080.getIsObstructed () == true )
169+ {
170+ Serial.print (" \t Obstructed" );
171+ writeCenteredStringToDisplay (" Obstructed" );
172+ }
173+ else
174+ {
175+ Serial.print (pm1Value);
176+ Serial.print (" \t " );
177+ Serial.print (pm25Value);
178+ writeStaticDisplayItems ();
179+ writeValuesToDisplay ();
180+ myOLED.display (); // actually command the display to show the scene
181+ }
182+
183+ Serial.println ();
184+ toggleHeartbeat ();
185+ }
186+ delay (100 );
187+ }
188+
189+ void showSplash ()
190+ {
191+ int x0 = (width - QW_BMP_SPARKFUN.width ) / 2 ;
192+ if (x0 < 0 )
193+ x0 = 0 ;
194+
195+ int y0 = (height - QW_BMP_SPARKFUN.height ) / 2 ;
196+ if (y0 < 0 )
197+ y0 = 0 ;
198+
199+ myOLED.erase ();
200+ myOLED.bitmap (x0, y0, QW_BMP_SPARKFUN);
201+ myOLED.display ();
202+ delay (2000 );
203+
204+ // Clear the screen
205+ myOLED.erase ();
206+ myOLED.display ();
207+ }
208+
209+ // Write the static display items to the screen
210+ void writeStaticDisplayItems ()
211+ {
212+ // clear the screen
213+ myOLED.erase ();
214+
215+ myOLED.setFont (&QW_FONT_5X7);
216+
217+ // draw the PM1 static text label
218+ // calculate the x position of the PM1 label
219+ // this is 1/4 the screen width minus 1/2 the width of the label
220+ int xPosPM1Text = myOLED.getWidth () / 4 - myOLED.getStringWidth (" PM1" ) / 2 ;
221+ myOLED.text (xPosPM1Text, 0 , " PM1" , 1 );
222+
223+ // draw the PM2.5 static text label
224+ // calculate the x position of the PM2.5 label
225+ // this is 3/4 the screen width minus 1/2 the width of the label
226+ int xPosPM25Text = (myOLED.getWidth () / 4 ) * 3 - myOLED.getStringWidth (" PM2.5" ) / 2 ;
227+ myOLED.text (xPosPM25Text, 0 , " PM2.5" , 1 );
228+
229+ // // draw the vertical separator line
230+ // myOLED.line(myOLED.getWidth() / 2, 0, myOLED.getWidth() / 2, myOLED.getHeight(), 1);
231+ // // draw a second line to make it more visible
232+ // myOLED.line(myOLED.getWidth() / 2 + 1, 0, myOLED.getWidth() / 2 + 1, myOLED.getHeight(), 1);
233+
234+ }
235+
236+ // Write the PM1 and PM2.5 values to the display
237+ void writeValuesToDisplay ()
238+ {
239+ // set the font to the large number font
240+ myOLED.setFont (&QW_FONT_LARGENUM);
241+
242+ // draw the PM1 value
243+ String toPrint = " blank" ;
244+ toPrint = String (int (pm1Value));
245+
246+ // calculate the x position of the PM1 value
247+ // we want it to be centered in the left half of the screen
248+ int xPosPM1Value = (myOLED.getWidth () / 4 ) - (myOLED.getStringWidth (toPrint) / 2 );
249+ myOLED.text (xPosPM1Value, 10 , toPrint, 1 );
250+
251+ // draw the PM2.5 value
252+ // calculate the x position of the PM2.5 value
253+ // we want it to be centered in the right half of the screen
254+ int xPosPM25Value = (myOLED.getWidth () / 4 ) * 3 - (myOLED.getStringWidth (toPrint) / 2 );
255+ toPrint = String (int (pm25Value));
256+ myOLED.text (xPosPM25Value, 10 , toPrint, 1 );
257+ }
258+
259+ // Write a string to the display that is centered horizontally and vertically
260+ void writeCenteredStringToDisplay (String toPrint)
261+ {
262+ // clear the screen
263+ myOLED.erase ();
264+
265+ // set the font to the 8x16 font
266+ myOLED.setFont (&QW_FONT_5X7);
267+
268+ // calculate the x position of the toPrint text
269+ // we want it to be centered in the screen horizontally
270+ // and vertically
271+ int xPosToPrint = (myOLED.getWidth () / 2 ) - (myOLED.getStringWidth (toPrint) / 2 );
272+ int yPosToPrint = (myOLED.getHeight () / 2 ) - (myOLED.getStringHeight (toPrint) / 2 );
273+
274+ // draw the string as text
275+ myOLED.text (xPosToPrint, yPosToPrint, toPrint, 1 );
276+ myOLED.display ();
277+ }
278+
279+ // blink the status LED
280+ void blinkStatLed ()
281+ {
282+ digitalWrite (StatLedPin, HIGH);
283+ delay (10 );
284+ digitalWrite (StatLedPin, LOW);
285+ }
286+
287+ // toggle "heartbeat" rectangle in the upper right corner of the screen
288+ void toggleHeartbeat ()
289+ {
290+ static bool bHeartbeat = false ;
291+
292+ // heartbeat rectangle is 3x3 pixels
293+ uint8_t rectWidth = 3 ;
294+ uint8_t rectHeight = 3 ;
295+
296+ // heartbeat rectangle is in the upper right corner of the screen
297+ uint8_t rectStartX = myOLED.getWidth () - rectWidth;
298+ uint8_t rectStartY = 0 ;
299+
300+ // draw the rectangle
301+ myOLED.rectangleFill (rectStartX, rectStartY, rectWidth, rectHeight, 1 );
302+
303+ // toggle the heartbeat
304+ bHeartbeat = !bHeartbeat;
305+
306+ // if the heartbeat is off, erase the rectangle
307+ if (!bHeartbeat)
308+ {
309+ myOLED.rectangleFill (rectStartX, rectStartY, rectWidth, rectHeight, 0 );
310+ }
311+
312+ myOLED.display ();
313+ }
0 commit comments