0

I am currently trying to set up the SM50 Ozone Sensor by Aeroqual for a project. I am trying to get the data digitally through the RS232 and RS485 port on the board. So far the Serial connection between the Arduino Uno and SM50 is failing to get established. The code fails at line number 61 ("if (ser->available())"). I am using a MAX232 and MAX485 chip to convert the signals to TTL.

The datasheet quotes: “Please note: the serial protocol is a proprietary binary protocol and is not ASCII. Hence a terminal program cannot be used to communicate with the SM50.”, could someone suggest an alternative way to acquire the data from the sensor? 



Module Name: Aeroqual SM50 Ozone Sensor

SM50 Datasheet

#include <SoftwareSerial.h>

#define SENSOR_STATUS_MASK 0x3
#define SENSOR_FLAG 0xAA
#define DATA_REPORT_FLAG 0x10
#define SENSOR_INFO_FLAG 0xFB
#define CONV_FACTOR_FLAG 0x2A

#define CMD_LEN 4
#define RET_LEN 15
#define WAIT_INTERVAL (1000UL * 60UL)

SoftwareSerial aeroqual_serial(10, 11);

/* Request codes */
static uint8_t data_request[CMD_LEN] = { 0x55, 0x1A, 0x00, 0x91 }; //receiver, data request, reserved, checksum
static uint8_t sensor_info[CMD_LEN]  = { 0x55, 0xFB, 0x00, 0xB0 }; //receiver, sensor info, reserved, checksum
static uint8_t conv_factor[CMD_LEN]  = { 0x55, 0x2A, 0x00, 0x81 }; //receiver, conversion, reserved, checksum

/* Report buffer */
static uint8_t ret_buffer[RET_LEN] = { 0 };

/* Struct to store/manipulate O3 data */
typedef struct sm50_data_report_s {
  uint8_t sensor;
  uint8_t data_report;
  uint32_t data; // float
  uint8_t reserved1[6];
  uint8_t status; // only b0-1 used
  uint8_t reserved2;
  uint8_t checksum;
} sm50_data_report_t;

/* Struct to store sensor info/specs */
typedef struct sm50_sensor_info_s {
  uint8_t sensor;
  uint8_t sens_info;
  uint8_t ver_no; // version * 10
  uint8_t disp_format;
  uint8_t name_len;
  char sensor_name[7]; // ASCII
  uint16_t reserved;
  uint8_t checksum;
} sm50_sensor_info_t;

/* Struct to store sensor info/specs */
typedef struct sm50_conv_factor_s {
  uint8_t sensor;
  uint8_t conversion;
  uint32_t factor; // float
  uint8_t reserved[8];
  uint8_t checksum;
} sm50_conv_factor_t;

/* Create structs for easy access */
static sm50_data_report_t sm50_o3_data;
static sm50_sensor_info_t sm50_sensor_info;
static sm50_conv_factor_t sm50_conv_info;

bool sm50_send_command(Stream *ser, uint8_t *cmd_buf) {
  if (ser->available()) {
    ser->write(cmd_buf, CMD_LEN);
    return true;
  }
  return false;
}

bool sm50_read_result(Stream *ser, uint8_t *ret_buf) {
  int max_tries = RET_LEN + 1;
  if (ser->available()) {
    while (max_tries > 0) {
      if (ser->peek() != SENSOR_FLAG) {
        ser->read();
        --max_tries;
      } else {
        ser->readBytes(ret_buf, RET_LEN);
        return true;
      }
    }
  }
  return false;
}

/* Could add additional checking with #define flags */
bool sm50_calc_checksum(uint8_t *buf) { // int data_flag?
  uint8_t sum = 0;
  for (int i = 0; i < RET_LEN - 1; ++i) {
    sum += buf[i];
  }
  /* Invert for checksum */
  sum = 0x0 - sum;

  if (sum == buf[RET_LEN - 1]) {
    return true;
  } else {
    return false;
  }
}

void print_buffer(uint8_t *buf, int len) {
  Serial.print("Buffer contents: ");
  for (int i = 0; i < len; ++i) {
    Serial.print("0x");
    Serial.print(buf[i], HEX);
    Serial.print(" ");
  }
  Serial.print('\n');
}


void setup() {
  Serial.begin(9600);
  delay(3000);

  /* Initialize sensor */
  aeroqual_serial.begin(9600);
  delay(3000);

  /* Read in sensor info for later use */
  if (sm50_send_command(&aeroqual_serial, sensor_info)) {
    if (sm50_read_result(&aeroqual_serial, ret_buffer)) {
      if (!sm50_calc_checksum(ret_buffer)) {
        Serial.print("Checksum invalid for data. Discarding.\n");
      } else {
        memcpy(&sm50_sensor_info, ret_buffer, RET_LEN);
        Serial.print("Copied SM50 sensor info into struct.\n");
      }
    } else {
      Serial.print("Failed to read sensor info output from SM50.\n");
    }
  } else {
    Serial.print("Failed to send sensor info command to SM50.\n");
  }
  /* Same for conv factor */
  if (sm50_send_command(&aeroqual_serial, conv_factor)) {
    if (sm50_read_result(&aeroqual_serial, ret_buffer)) {
      if (!sm50_calc_checksum(ret_buffer)) {
        Serial.print("Checksum invalid for data. Discarding.\n");
      } else {
        memcpy(&sm50_conv_info, ret_buffer, RET_LEN);
        Serial.print("Copied SM50 conversion factor into struct.\n");
      }
    } else {
      Serial.print("Failed to read conversion factor output from SM50.\n");
    }
  } else {
    Serial.print("Failed to send conversion factor command to SM50.\n");
  }
}


void loop() {
  /* Read data every X minutes? */
  if (sm50_send_command(&aeroqual_serial, data_request)) {
    if (sm50_read_result(&aeroqual_serial, ret_buffer)) {
      if (!sm50_calc_checksum(ret_buffer)) {
        Serial.print("Checksum invalid for data. Discarding.\n");
      } else {
        memcpy(&sm50_o3_data, ret_buffer, RET_LEN);
        Serial.print("Copied SM50 O3 data into struct.\n");
      }
    } else {
      Serial.print("Failed to read O3 data output from SM50.\n");
    }
  } else {
    Serial.print("Failed to send data request command to SM50.\n");
  }

  /* Swap float endianness and print to console*/
  sm50_o3_data.data = ((sm50_o3_data.data >> 8) & 0x00FF00FF) |
                      ((sm50_o3_data.data & 0x00FF00FF) << 8); // swap bytes
  sm50_o3_data.data = (sm50_o3_data.data >> 16) |
                      (sm50_o3_data.data << 16); // swap words
  Serial.print("The ozone (O3) concentration in ppm is: ");
  Serial.print(sm50_o3_data.data, 4);

  switch (sm50_o3_data.status & SENSOR_STATUS_MASK) {
    case (0x0):
      Serial.print("Sensor status: OK!\n");
      break;
    case (0x1):
      Serial.print("Sensor status: Failure!\n");
      break;
    case (0x3):
      Serial.print("Sensor status: Ageing\n");
      break;
    default:
      Serial.print("Invalid sensor status.\n");
      break;
  }

  delay(WAIT_INTERVAL);
}
8
  • "So far the Serial connection between the Arduino Uno and SM50 is failing to get established" - What exactly does this mean? How exactly is it failing? Your code already has many parts handling the serial data. Do you actually get data from the sensor? If yes, is it the data, that you expect? How exactly have you connected it? – chrisl Dec 21 '20 at 13:41
  • Hey, when I say it is failing: in setup(), the function sm50_send_commands is called in the first if block. Here the function returns false every time. I have connected the sensor's RS232 port to a MAX232 breakout board and the TTL output to the Tx/Rx pins on my Arduino Uno. I have set the baud rate to 9600 (according to the datasheet). I don't see any data as of now. – Surya Teja Dec 21 '20 at 13:55
  • Why does the function sm50_send_command() only send data, when it received something? I guess the sensor only sends information, when you request them. That would mean, that you never send anything, because you didn't receive anything. And you didn't receive anything, because you didn't send anthing. – chrisl Dec 21 '20 at 14:04
  • The datasheet mentions that for RS232, a data request need not be made and that the sensor sends data every 70 seconds (approximately). – Surya Teja Dec 21 '20 at 14:08
  • But that will still mean, that most likely every call of the send function will fail in setup(). Why do you only want to send, if there is something in the receive buffer? that doesn't make sense to me – chrisl Dec 21 '20 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.