0

Ultimate GPS v3 and PMS5003 - SoftwareSerial Issue
Moderators: adafruit_support_bill, adafruit

Please be positive and constructive with your questions and comments.

Ultimate GPS v3 and PMS5003 - SoftwareSerial Issue

by biod101 on Tue May 15, 2018 2:23 pm

Hello,

I am currently working on an air-quality monitoring data logger with GPS capabilities. The idea is to strap it to my bike and generate a map of air quality based on different commutes (main thoroughfares vs. residential streets vs. bike paths). I am starting simple and using an Ultimate GPS Breakout (Product #746) along with with a PM2.5 Air Quality Sensor.

Full disclosure – I did not purchase the PMS5003 on Adafruit, but it works fine and is the same as your product id 3686. I have purchased both Adafruit's Ultimate GPS breakout (746) as well as the Ultimate GPS Logger Shield (1272).

Below is a photo of the hardware:

IMG_1138.JPG
IMG_1138.JPG (560.07 KiB) Viewed 279 times


For starters, I’m trying to keep things very simple so I’m just trying to get the PM Sensor and GPS to work together with no datalogging. Along these lines, I first tried a simple GPS sketch to see ensure I could register a location without measuring air quality– no problem; here is the code for the circuit shown above. (I am not showing the output since the location is for my home, but it does work):

Code: Select all | TOGGLE FULL SIZE
#include <SoftwareSerial.h>

SoftwareSerial gpsSerial(8, 7); // RX, TX (TX not used)
const int sentenceSize = 80;

char sentence[sentenceSize];

void setup()
{
  Serial.begin(9600);
  gpsSerial.begin(9600);
}

void loop()
{
  static int i = 0;
  if (gpsSerial.available())
  {
    char ch = gpsSerial.read();
    if (ch != '\n' && i < sentenceSize)
    {
      sentence[i] = ch;
      i++;
    }
    else
    {
     sentence[i] = '\0';
     i = 0;
     Serial.println(sentence);
     //displayGPS();
    }
  }
}

void displayGPS()
{
  char field[20];
  getField(field, 0);
  if (strcmp(field, "$GPRMC") == 0)
  {
    Serial.print("Lat: ");
    getField(field, 3);  // number
    Serial.print(field);
    getField(field, 4); // N/S
    Serial.print(field);
   
    Serial.print(" Long: ");
    getField(field, 5);  // number
    Serial.print(field);
    getField(field, 6);  // E/W
    Serial.println(field);
  }
}

void getField(char* buffer, int index)
{
  int sentencePos = 0;
  int fieldPos = 0;
  int commaCount = 0;
  while (sentencePos < sentenceSize)
  {
    if (sentence[sentencePos] == ',')
    {
      commaCount ++;
      sentencePos ++;
    }
    if (commaCount == index)
    {
      buffer[fieldPos] = sentence[sentencePos];
      fieldPos ++;
    }
    sentencePos ++;
  }
  buffer[fieldPos] = '\0';
}


Next, I tried just running the PMS5003 sample sketch published by Adafruit without talking to the GPS. Again, no problem – here is the code and the output:

Code: Select all | TOGGLE FULL SIZE
// On Leonardo/Micro or others with hardware serial, use those!
// uncomment this line:
// #define pmsSerial Serial1

// For UNO and others without hardware serial, we must use software serial...
// pin #2 is IN from sensor (TX pin on sensor), leave pin #3 disconnected
// comment these two lines if using hardware serial
#include <SoftwareSerial.h>
SoftwareSerial pmsSerial(2, 3);

void setup() {
  // our debugging output
  Serial.begin(9600);

  // sensor baud rate is 9600
  pmsSerial.begin(9600);
}

struct pms5003data {
  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 pms5003data data;
   
void loop() {
  if (readPMSdata(&pmsSerial)) {
    // 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 > 50 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;
}



OuputPM.png
OuputPM.png (176.39 KiB) Viewed 279 times


Here comes the tricky part – getting both the PMS5003 and GPS to work in the same sketch. I merged both sketches which compile fine. I understand that I can only listen to one SoftwareSerial connection at a time, and must let my Arduino Uno know which connection to listen to via the listen() function. I’ve done so in my code, but unfortunately, the code fails when I try to read the PMS5003 sensor, and then will not open the GPS connection for reporting.

Here is the code and the output. I've shortened the code for readability and for the purposes of focusing on where it is failing- specifically at the readPMSdata() function in the conditional at line 105.

Code: Select all | TOGGLE FULL SIZE
#include <SoftwareSerial.h>

SoftwareSerial pmsSerial(2, 3);
SoftwareSerial gpsSerial(8, 7); // RX, TX (TX not used)

const int sentenceSize = 80;

char sentence[sentenceSize];

struct pms5003data {
  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 pms5003data data;

void setup()
{
  Serial.begin(9600);
  gpsSerial.begin(9600);
  pmsSerial.begin(9600);
}

void loop()
{

  pmsSerial.listen();
  readPMSdata(&pmsSerial);
  // doesn't matter if I execute the above two lines
  // at the beginning or the end of the loop.
 
  gpsSerial.listen();
 
  static int i = 0;
  if (gpsSerial.available())
  {
    char ch = gpsSerial.read();
    if (ch != '\n' && i < sentenceSize)
    {
      sentence[i] = ch;
      i++;
    }
    else
    {
     sentence[i] = '\0';
     i = 0;
     Serial.println(sentence);
     //displayGPS();
    }
  }

// pmsSerial.listen();
// readPMSdata(&pmsSerial);
 
}

void displayGPS()
{
  char field[20];
  getField(field, 0);
  if (strcmp(field, "$GPRMC") == 0)
  {
    Serial.print("Lat: ");
    getField(field, 3);  // number
    Serial.print(field);
    getField(field, 4); // N/S
    Serial.print(field);
   
    Serial.print(" Long: ");
    getField(field, 5);  // number
    Serial.print(field);
    getField(field, 6);  // E/W
    Serial.println(field);
  }
}

void getField(char* buffer, int index)
{
  int sentencePos = 0;
  int fieldPos = 0;
  int commaCount = 0;
  while (sentencePos < sentenceSize)
  {
    if (sentence[sentencePos] == ',')
    {
      commaCount ++;
      sentencePos ++;
    }
    if (commaCount == index)
    {
      buffer[fieldPos] = sentence[sentencePos];
      fieldPos ++;
    }
    sentencePos ++;
  }
  buffer[fieldPos] = '\0';
}


boolean readPMSdata(Stream *s) {
  if (! s->available()) {
    Serial.println("fail");
    return false;
  }
  else
    Serial.println("OK!");
 
  // 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;
}


fail.png
fail.png (100.3 KiB) Viewed 279 times


I've reviewed the Arduino documentation for SoftwareSerial, and even played with the example listen function via the sample sketch shown below, and have had no problems:

Code: Select all | TOGGLE FULL SIZE
#include <SoftwareSerial.h>

// software serial : TX = digital pin 10, RX = digital pin 11
SoftwareSerial portOne(10, 11);

// software serial : TX = digital pin 8, RX = digital pin 9
SoftwareSerial portTwo(8, 9);

void setup()
{
  // Start the hardware serial port
  Serial.begin(9600);

  // Start both software serial ports
  portOne.begin(9600);
  portTwo.begin(9600);

}

void loop()
{
  portTwo.listen();

  if (portOne.isListening()) {
   Serial.println("Port One is listening!");
}else{
   Serial.println("Port One is not listening!");
}

  if (portTwo.isListening()) {
   Serial.println("Port Two is listening!");
}else{
   Serial.println("Port Two is not listening!");
}

}


I also tried using the formal Adafruit GPS libraries (same result), tried using the Adafruit GPS shield making sure to reassign the SoftwareSerial pins in a new sketch (same result), and even tried reordering the listen functions, but I just can't seem to get the two pieces of hardware to place nicely together.

As a final alternative, it appears someone has managed to make this work on a Mega as per this link: http://blog.e-ao.org/#post11, but I don't think I should have to go the extreme of purchasing a Mega for this simple project. Is there any way I can make this work with a simple Arduino Uno?

Thanks in advance for any insight.

biod101
 
Posts: 100
Joined: Sun Apr 19, 2015 4:21 pm

Re: Ultimate GPS v3 and PMS5003 - SoftwareSerial Issue

by adafruit_support_mike on Tue May 15, 2018 11:43 pm

Try using Paul Stoffregen's AtlSoftSerial library instead of SoftwareSerial:

https://www.pjrc.com/teensy/td_libs_AltSoftSerial.html

It's designed to behave better with multiple connections.

adafruit_support_mike
 
Posts: 58825
Joined: Thu Feb 11, 2010 2:51 pm

Re: Ultimate GPS v3 and PMS5003 - SoftwareSerial Issue

by Dgt211 on Sat Apr 27, 2019 8:20 pm

Hi Adafruit Support and biod101, I'm running into the same problem with parts I bought from Adafruit, but it's not clear to me how to incorporate altserial into my code to make it work. Can you post how I would do this? My code and wiring is exactly the same as in the original post above.

Dgt211
 
Posts: 19
Joined: Tue Oct 07, 2014 10:46 pm

Please be positive and constructive with your questions and comments.