This code works on an Arduino Nano connected to a Sharp Dust Sensor and a 2 line LCD. It shows how to get the correct timing for the pulse that controls the LED in the sensor. The display shows the minimum and maximum voltages measured from the sensor output, in 5 second sampling periods.
This version doesn’t do any conversion of the output voltage to a dust density.
/* Interface to Sharp GP2Y1010AU0F Particle Sensor Modified from Program by Christopher Nafis Written April 2012 Changes (Sept 2016): LCD Display ISR code for ADC */ #include <LiquidCrystal.h> #include <stdlib.h> const byte adcPin = 0; // Variables that can be changed by an ISR must be declared as volatile volatile int adcReading; volatile boolean adcDone; boolean adcStarted; int dustPin=0; int ledPower=2; int delayTime=280; int delayTime2=40; float offTime=9580; int dustVal=0; int i=0; int c=0; int adcMin=1023; int adcMax=0; float ppm=0.0; char s[32]; float voltage = 0.0; float vMin = 0.0; float vMax = 0.0; float dustdensity = 0.0; float ppmpercf = 0.0; // Define the pins used by the LCD LiquidCrystal lcd(7,8,9,10,11,12); void setup(){ pinMode(ledPower,OUTPUT); lcd.begin(16,2); lcd.print("Starting"); delay(1000); i=0; c=0; ppm=0.0; // set the analog reference (high two bits of ADMUX) and select the // channel (low 4 bits). this also sets ADLAR (left-adjust result) // to 0 (the default). ADMUX = bit (REFS0) | (adcPin & 0x07); } // ADC complete Interupt Service Routine ISR (ADC_vect) { byte low, high; // read the result registers and store value in adcReading // Must read ADCL first low = ADCL; high = ADCH; adcReading = (high << 8) | low; adcDone = true; } // end of ADC_vect void loop(){ do { i=i+1; digitalWrite(ledPower,LOW); // power on the LED delayMicroseconds(delayTime); // wait 280uS // Check the conversion hasn't been started already if (!adcStarted) { adcStarted = true; // start the conversion ADCSRA |= bit (ADSC) | bit (ADIE); } delayMicroseconds(delayTime2); // wait another 40uS or so to give 320uS pulse width digitalWrite(ledPower,HIGH); // turn the LED off // give the ADC time to complete then process the reading delayMicroseconds(100); if (adcDone) // adcDone is set to True by the ISR, called when the conversion is complete. { adcStarted = false; if (adcMin > adcReading) adcMin = adcReading; // 5 second Minimum if (adcMax < adcReading) adcMax = adcReading; // 5 second Maximum c = c+1; // count of readings adcDone = false; } delayMicroseconds(offTime); // wait for the remainder of the 10mS cycle time } while (i <500); //repeat for 5 seconds // Convert ADC max/min readings in last 5 seconds (0-1023) to voltages vMin = adcMin * 5.0 / 1024; vMax = adcMax * 5.0 / 1024; String dataString = "vMin:"; dataString += dtostrf(vMin, 9, 4, s); lcd.setCursor(0,1); lcd.print(dataString); dataString = "vMax:"; dataString += dtostrf(vMax, 9, 4, s); lcd.setCursor(0,0); lcd.print(dataString); i=0; c=0; adcMin=1023; adcMax=0; }
Andy,
Interesting good work, I am new to arduino, after going through your code i have few questions to ask.I copied your code and tested. In one of test case i.e by inserting a pen/pencil in dust collection hole of sensor , i am not getting peak voltage around 3.15v at the output. which i used to get by using simple analogread function as mentioned in Christopher Nafis program. 2) Whether i need to take average of Vmax and Vmin? 3) It would be great and helpful for beginners, if you provide explanation on ADMUX = bit (REFS0) | (adcPin & 0x07) and ADCSRA |= bit (ADSC) | bit (ADIE);
4) I wanted to know utilization of variables c and ppm. Mainly thinking about variable “C”.
Regards,
Vishwa
Hi Vishwanath,
Thanks for your interest.
Answers to your questions…
1: Putting a pen/pencil in the hole.
I never really understood why this would produce the maximum reading, though it does seem to be an accepted part of testing this sensor. I think the sensor works by measuring the IR reflected off of dust particles floating in the hole, so what you’d want for a maximum reading would really be a small mirror to reflect as much of the IR from the LED as possible into the detector, rather than a pen to block it. Using a pen to get a maximum reading would only make sense to me if the sensor is measuring the attenuation of the light by dust particles, in which case you’d need a lot of dust to get much of a reading.
2:Vmax / Vmin
This code is really just a way of displaying the output from the sensor on the LCD. If you just show the last reading on the LCD it would be changing very fast so would be difficult to read. Also, the code to write to the LCD would upset the timing too. So what this does instead is take a series of readings for 5 seconds, then displays the minimum and maximum read during the last 5 seconds. Interpretation of the output needs a bit more thought. Looking at the output on a scope you can see that most readings are close to vMin and you get just a few that are much higher. Maybe this is the sensor detecting individual dust particles… I’m not sure. One of the Sharp application notes suggests that code should correct for the baseline by subtracting the long term minimum value from the actual reading.
3: ADMUX / ADCSRA
All this is doing is pushing values into registers to directly control the ADC. “|” means bitwise OR; “X|=Y” means “X = X|Y”. Using OR sets the bits in a value, in this case a register, without changing the other bits. AnalogRead() does the same thing. The difference is that the code doesn’t wait for a response, it starts the ADC then carries on without waiting for the result so that it can turn the LED off at the correct time. When the conversion is complete this fires an interrupt, which runs the ISR to read the value.
4: c and ppm variables
These are sort of leftovers from earlier versions. The c variable I used for debugging, to check the number of times the reading was being taken. If the ISR wasn’t being called the code inside the if{} wouldn’t get executed.
ppm was used for my various attempts at getting a quanative air quality reading. I’m not really happy with any of them, so I took it out for now.
Regards,
Andy.
Hello Andy
thanks so much for all your careful work and this posting. I’d given up on the Sharp and have been testing some of the laser alternatives (Nova Fitness SDS011 and Plantower PMS3003). I have what seems like a reasonable working dust logger already based on the Samyoung sensor but will now go back to the Sharp and out of interest see if the shortcomings (a lot of zeros among the data) have been addressed with your code. I’ve calibrated my Samyoung logger against a cheap particulate monitor (Speck) sold by CMU which also uses the Samyoung sensor. They are individually calibrated against a proper particulate monitor but like the Sharp it can’t sort particle sizes so the calibration is only a correlation of some kind. It does happen to agree with the local published values for PM2.5 on the whole and some curve-fitting matched mine to the Speck OK. https://www.specksensor.com/
Hi Bruce,
Thanks for the comment.
You shouldn’t ever get a reading of 0 volts from the Sharp sensor, though code that gives an estimate of PPM or dust density as the output would be subtracting an offset so that could give a reading of zero. The code above displays the max and min voltages measures from the sensor in 5 second intervals. In my tests this is about 0.2v most of the time, rising to a max of about 3.6v if you put something in the hole like a small screwdriver.
I’ve had various attempts at coding an air quality value rather than the voltage. I’m not really happy with any of them but some factors it needs to consider are…
– Over it’s usable range the output is basically linear, so Voltage measured will be v=mQ+c where m is the dust density coefficient (from the datasheet), Q is dust density and c is an offset.
– The value for c has to account for internal reflection, ambient temperature, particles adhering to the inside of the sensor and degradation of the LED over time. I think it needs to be a slowly changing value rather than a constant and the code needs a way of updating it sensibly.
– The sensor is quite sensitive and amount of ‘dust’ required to get to the top of the usable range is smaller than I expected. It’s pretty much the case that if you can see it (smoke) in the air it will max out the sensor.
– Watching the output on an oscilloscope you can see that most readings are near the baseline reading, with a number of significantly higher single readings. This could be individual large dust particles floating through the sensor, so a threshold/count method might give a useful indication.
– Having a 5v supply and the specified capacitor in the driving circuit are essential. I think the IR LED inside this sensor works like a small flash gun.
– Keep in mind that the sensor is actually measuring the IR light that reaches the detector, nothing more. Anything inferred from that reading about dust density could be completely wrong.
Maybe I’ll write a post with some of the things I’ve tried…
Hello Andy,
one of the possible improvements could be to use timer interrupt for calling ADC, instead of doing all in the main loop with delayMicroseconds(offTime). This way we can obtain a very stable sampling frequency and use BiQuad low-pass filter to get a smooth approximation curve. I implemented it this way, integrated your ADC code and now am very happy with the result.
Here’s the my code, with SD logging included: https://github.com/vladkozlov69/SHARP_DUST_LOGGER/blob/master/SHARP_DUST_LOGGER.ino
Thanks for your interest. I never really thought about filtering the output of the sensor. Looking at the output using an oscilloscope a lot of the the time the output is fairly stable with the occasional higher one. I suspected this is a large dust particle passing through the sensor. In that case you’d want to average the reading, including the peaks. I’d be interested to see how your readings vary over time.
You’ve probably read it, but if not google “Enabling Low-Cost Particulate Matter Measurement for Participatory Sensing Scenarios”. Budde et al. 2013. It put me off the Sharp really. However I have your Sharp + Nano and LCD setup going nicely and learned a lot from doing so! Thanks.
I hadn’t come across that before, but it does seem to have the same concerns about getting a quantitive reading as I do. It’s interesting that they say the sensor output is noisy. I did have a version with a small PC fan as I wanted to see if having a slight airflow through the sensor made any difference, but it added so much noise to the input signal I left it out.
The variation between indoor and outdoor readings, which they suggest could be down to temperature is interesting too. It would be easy enough to add a temperature sensor then correct c for temperature.
Glad the Nano + LCD worked for you. I think hardly anyone reads this stuff, but nice to know it’s of interest to someone.
Hi Andy, thanks for the work you’ve done on this and for sharing it! I saw your comment on Cyrille’s blog post about the AnalogRead() timing issue and ended up here. I’m using your code on an Uno with no issues. Interesting to hear about the possible changes with temperature, too.
Great post.
Just to improve code readability a bit:
#define adcPin = A0;
…
ADMUX = bit (REFS0) | ((adcPin – PIN_A0) & 0x07);