I have tried and tried pulling the library across to my build, but it keeps failing in endless different parts.
The library also calls for a second library (Adafruit_I2CDevice.h) which appears to reside in Adafruit_BusIO. I think is the root cause of the issue, as it is referring to Adafruit i2C interface rather than the Particle Argon i2c?..
I have tried replacing all of the <Arduino.h> with “Particle.h” and moving the .h and .cpp files into separate src folders. I’m dying here…
Any help would be amazing after I spent a good month getting these sensors to speak to InfluxDB, I now really need this data to send. All my other sensors seem to be relatively ok, but this one is killing me.
I would personally hack the Adafruit_PM25AQI.h like this, the Wire library is included with Particle so you don’t need to include the Adafruit_I2CDevice which is to handle a range of different devices. The .cpp is very simple; begin() just needs Wire.begin(); and the reading of data needs to start with Wire.requestFrom(address, quantity); then Wire.read() into the receive buffer until no more available.
Thanks so much for this, I have edited the .h and it looks pretty clean. but not entirely clear what you mean with the .cpp file.
I have made a bit of a dogs breakfast of it tbh.
Because I removed the reference to ‘Adafruit_I2CDevice *i2c_dev = NULL;’ in the .h it now cant define what the i2c_dev is. But, am I reading it right that as I know I am using i2c in this instance, I can just get rid of this definition and not have the ‘if’ statements?
I have gotten in a little out of my depth here, but it looks like I need to strip the references to Adafruit_I2CDevice. But then I end up with a mostly empty file that doesn’t appear to do anything?
Ok, so firstly, thanks again, I actually didnt understand how the .h and .cpp linked before this, so thank’s for forcing me to do some more digging. Here is where I am at the moment:
Adafruit_PM25AQI.h
#include "Particle.h"
// the i2c address
#define PMSA003I_I2CADDR_DEFAULT 0x12 ///< PMSA003I has only one I2C address
// /**! Structure holding Plantower's standard packet **/
typedef struct PMSAQIdata {
uint16_t framelen; ///< How long this data chunk is
uint16_t pm10_standard, ///< Standard PM1.0
pm25_standard, ///< Standard PM2.5
pm100_standard; ///< Standard PM10.0
uint16_t pm10_env, ///< Environmental PM1.0
pm25_env, ///< Environmental PM2.5
pm100_env; ///< Environmental PM10.0
uint16_t particles_03um, ///< 0.3um Particle Count
particles_05um, ///< 0.5um Particle Count
particles_10um, ///< 1.0um Particle Count
particles_25um, ///< 2.5um Particle Count
particles_50um, ///< 5.0um Particle Count
particles_100um; ///< 10.0um Particle Count
uint16_t unused; ///< Unused
uint16_t checksum; ///< Packet checksum
} PM25_AQI_Data;
class Adafruit_PM25AQI
{
public:
Adafruit_PM25AQI();
bool begin(); // <-- Do i need anything in here to return data from the .cpp?
bool read(PM25_AQI_Data *data);
private:
uint8_t _readbuffer[32];
};
Adafruit_PM25AQI.cpp
#include "Adafruit_PM25AQI.h"
/*!
* @brief Instantiates a new PM25AQI class
*/
Adafruit_PM25AQI::Adafruit_PM25AQI() {}
bool Adafruit_PM25AQI::begin(){
Wire.begin();
return true;
}
/*!
* @brief Setups the hardware and detects a valid UART PM2.5
* @param data
* Pointer to PM25_AQI_Data that will be filled by read()ing
* @return True on successful read, false if timed out or bad data
*/
bool Adafruit_PM25AQI::read(PM25_AQI_Data *data)
{
uint8_t buffer[32];
uint16_t sum = 0;
Wire.requestFrom(PMSA003I_I2CADDR_DEFAULT, 32);
Wire.read();
if (!data) {
return false;
}
// Check that start byte is correct!
if (buffer[0] != 0x42) {
return false;
}
// get checksum ready
for (uint8_t i = 0; i < 30; i++) {
sum += buffer[i];
}
// 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) {
return false;
}
// success!
return true;
}
The problem I am getting is that in my .ino file trying to run this, I am getting a failure when I call:
if (!aqi.read(&data))
{
Particle.publish("Serial","Could not read from AQI",PRIVATE);
delay(500); // try again in a bit!
return;
}
The address I am using is the default address 0x12 in the Wire.read(address, 32)?
In your Adafruit_PM25AQI::read() you are never reading data into your buffer, hence whenever you check that buffer (e.g. here if (buffer[0] != 0x42)) you are working on uninitialized data.
Instead of that single Wire.read(); which only reads one byte but immediately discards it you can try Wire.readBytes((char*)buffer, sizeof(buffer)); which actually tries to read 32 bytes and feeding it into the buffer.
For good practice you should also catch the return value of that call and compare it against your expected number of bytes to detect a timeout condition (e.g. when the I2C client won’t send all data in time).
I have amended the Adafruit_PM25AQI::read() to now be:
bool Adafruit_PM25AQI::read(PM25_AQI_Data *data)
{
uint8_t buffer[32];
uint16_t sum = 0;
Wire.requestFrom(PMSA003I_I2CADDR_DEFAULT, 32);
//Wire.read();
Wire.readBytes((char *)buffer, sizeof(buffer));
if (!data)
{
return false;
}
// Check that start byte is correct!
if (buffer[0] != 0x42)
{
return false;
}
// get checksum ready
for (uint8_t i = 0; i < 30; i++)
{
sum += buffer[i];
}
// 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)
{
return false;
}
// success!
return true;
}
But it seems to be hanging as soon as my .ino calls the read(). It reports everything up until it hits the loop.
Apologies if the use of Particle.publish as a defacto Serial isnt kosher… It seems easier to have this dumping to a terminal than playing with a serial.
#include "Adafruit_PM25AQI.h"
Adafruit_PM25AQI aqi = Adafruit_PM25AQI();
void setup()
{
Serial.begin(115200);
Particle.publish("Serial", "Adafruit PMSA003I Air Quality Sensor", PRIVATE);
// Wait one second for sensor to boot up!
delay(1000);
// If using serial, initialize it and set baudrate before starting!
aqi.begin();
// There are 3 options for connectivity!
if (!aqi.begin())
{ // connect to the sensor over I2C
Particle.publish("Serial", "Could not find PM 2.5 sensor!", PRIVATE);
while (1)
delay(10);
}
Particle.publish("Serial", "PM25 found!", PRIVATE); // <-- THIS IS THE LAST MESSAGE TO COME THROUGH
}
void loop()
{
PM25_AQI_Data data;
if (!aqi.read(&data))
{
Particle.publish("Serial","Could not read from AQI",PRIVATE);
delay(500); // try again in a bit!
return;
}
Particle.publish("serial","AQI reading success",PRIVATE);
aqi.read(&data);
}
Not sure if this is a stupid question, but where does the ‘data’ in the below get populated?
You'r loop() is also bound to violate the publishing rate limit.
And why are you calling aqi.read() twice in loop()?
Can you explain that?
I usually don't have issues with Serial.println() or a SerialLogHandler
You should definetly try adding Serial.println() statements into your read() function to see where and why the call fails.
Additionally you could opt for int rather than bool as return value and hand back an error code to tell you where it failed.
This only checks whether the pointer data has got a non-NULL address.
Since you are passing &data into the function that parameter will definetly not be NULL.
I'd also advise against the use of number literals like here
Nope, this will only check whether you can re-calculate the checksum and make it match the respective value that was calculated and transmitted by the sensor.
But this will not tell you whether you got all bytes read as expected but will only fail without giving a reason - or in a highly unlikely case may even check out although the data may actually be wrong
I can break my statement down:
"catch the return value"
int bytesRead;
bytesRead = Wire.readBytes((char *)buffer, sizeof(buffer));
"compare it against your expected number"
if (bytesRead != sizeof(buffer))
return bytesRead - sizeof(buffer); // return number of bytes that couldn't be read (as negative)
Together with
and some values like this (although I'd use an enum for the error codes)
if (!data)
return -100; // indicate no valid data parameter provided
...
if (buffer[0] != 0x42)
return -101; // indicate bad starting byte
...
if (sum != data->checksum)
return -102; // indicate bad checksum
...
return 0; // indicate success
You can put a switch statement in your calling function to deal with the respective errors specifically.
Great, this is really helpful, I'll get to integrating it!
Interestingly, I have added a bunch of Serial posts in each step of the code and it grinds to a dead halt as commented below (note, I also get the "Serial", "PM25 found!" from the void setup() before this). The issue appears to be in the Wire.requestFrom():
bool Adafruit_PM25AQI::read(PM25_AQI_Data *data)
{
uint8_t buffer[32];
uint16_t sum = 0;
Particle.publish("Serial","001",PRIVATE); // LAST MESSAGE I SEE IS THIS
delay(1000);
Wire.requestFrom(PMSA003I_I2CADDR_DEFAULT, sizeof(buffer)); // PRESUME THIS IS KILLING IT?
Particle.publish("Serial", "002", PRIVATE); // NEVER SEE THIS MESSAGE...
delay(1000);
//Wire.read();
Wire.readBytes((char *)buffer, sizeof(buffer));
Particle.publish("Serial", "003", PRIVATE);
delay(1000);
...
I get the "Serial", "001" message, but the 002 never arrives and my console is telling me the device becomes unreachable.
The device wont even respond to an OTA at this point and I have to manually put it into DFU mode and Local Flash any changes.
If i flash something basic on there it runs fine and can update at will without a hard DFU set.
That might be causing it to hang, or memory leak, or not complete? Or, is the read address for an i2c device different to it's address (0x12 in this case)?
Because of this, I can't even get to the steps of running the check for a complete read/timeout.
Edit: If i unplug the sensor, the whole chain of "Serial", "001, 002, 003, ..." run through and I get the "Serial", "Could not read from AQI" coming through... It seems to be at the point it is reading from the sensor it never completes?
Can you run an I2C scanner sketch to see whether you can communicate with the device at all and are using the correct address?
You can also use Safe Mode to allow for OTA updates.
Sure, when your device isn't actually returning 32 bytes in response to the request that could cause the issue.
That might also be the case (hence the suggestion to find the actual I2C client address via the scanner sketch).
I haven't read the datasheet for that sensor but some sensors use some internal addressing scheme to request particular registers but these would not be provided via the Wire.requestFrom() call. Wire.requestFrom() wants to be told the I2C address of the client. The internal "register address" needs to be pre-set via a separate Wire.write() to the I2C client address before requesting the data from that register.
Don't need pull-ups as they are integrated on the board.
Have used this same shield for other 12c devices and has worked fine, so I don't think it's hardware. It takes VIN ranging from 3-5V as it has an onboard regulator.
After a quick look in the Adafruit library I see that sensor would also support UART mode - just in case you can’t solve the I2C issue.
However, given the situation, I’d track back a bit and remove some complexity from the project by ignoring the Adafruit_PM24AQI class and try to test raw communication without interpreting the data but merely using Wire.available() and Wire.read() in a loop to see what’s going on at all.
The I2C scanner obviously shows that there is no issue with using Serial.println(), so you should use that instead of Particle.publish().
You can use the I2C scanner sketch as a basis for your tests.
Indeed, I just use Particle.publish() so I can get minimal messages remotely, and it's become a habit rather than firing up putty and connecting to the COM port.
void setup()
{
Wire.begin();
Serial.begin(115200);
delay(5000); // Wait for me to open the serial monitor.
}
void loop()
{
Serial.println("\nNext Read:");
Wire.requestFrom(0x12, 32);
while (Wire.available())
{
char c = Wire.read();
Serial.print(c);
}
Serial.println("");
Serial.println("Read Complete. Waiting for next loop()");
delay(1000);
}
I got an export of:
Next Read:
BM
▒fU ▒▒
Read Complete. Waiting for next loop()
Next Read:
▒fY▒▒
Read Complete. Waiting for next loop()
Next Read:
▒fY▒▒
Read Complete. Waiting for next loop()
Next Read:
w_V▒▒
Read Complete. Waiting for next loop()
Next Read:
BMw_V▒▒
Read Complete. Waiting for next loop()
Next Read:
BM8FL▒{
Read Complete. Waiting for next loop()
Next Read:
5E▒.
Read Complete. Waiting for next loop()
Next Read:
BM
5E▒(
Read Complete. Waiting for next loop()
Next Read:
DJL▒▒
Read Complete. Waiting for next loop()
Next Read:
DJL▒▒
Read Complete. Waiting for next loop()
Next Read:
▒uO▒0
Read Complete. Waiting for next loop()
Next Read:
▒yO▒@
Read Complete. Waiting for next loop()
I've checked the baud rate, and it matches the serial monitor. I also tried it at 9600 to be sure and it's a similar illegibility).
After letting it run for a while it has started to become more consistent with:
Next Read:
BM
{▒u
Read Complete. Waiting for next loop()
Next Read:
BM
{▒u
Read Complete. Waiting for next loop()
Next Read:
BM
9▒C
Read Complete. Waiting for next loop()
Next Read:
BM
9▒C
Read Complete. Waiting for next loop()
Next Read:
BM
▒"9▒▒
Read Complete. Waiting for next loop()
Would it be a fair assumption that the "BM" at the beginning means it is putting out a consistent starting byte, which correlates with the:
// Check that start byte is correct!
if (buffer[0] != 0x42)
{
return false;
}
Edit: Looked it up. 0x42 in ASCII is Uppercase B! Nice! Edit 2: Been watching it for a while, consistently starting with BM now.
Should I be going into the .cpp and editing the:
Wire.readBytes((char *)buffer, sizeof(buffer));
With something that reads similar to the below?:
int i = 0;
while (Wire.available())
{
buffer[i] = Wire.read();
++i;
}
Edit 3: nope... still hanging in the same place...
Edit 4: oh... my... God... I forgot to include Wire.begin()...
Now am through to it spitting out "Could not read from AQI" in a 1s loop, so now will work on your data checker and see what the issue is.
These “funny” characters you see stem from the fact that you are receiving mostly binary data which will render mainly non-printable characters.
Try Serial.printlnf("0x%02x %3d (%c)", c, c, c); instead. This will give you the HEX and DEC representation of the byte (and the ASCII in parentheses).
With that you can calculate the checksum manually and compare with the expected value.