Hi.
I am building an I2C driver for the Sensirion SPS30 particle sensor (datasheet: https://no.mouser.com/datasheet/2/682/Sensirion_PM_Sensors_SPS30_Datasheet-1888453.pdf).
But I am having an awful lot of trouble getting it to run on the nRF9160DK.
DK code:
#include "sps30_I2C.h"
#include <dk_buttons_and_leds.h>
#include <sys/printk.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(dk, CONFIG_APP_LOG_LEVEL);
static const struct device *die_dev;
static struct sps30_data sps30;
static int * dec(float num) {
static int r[2];
r[0] = (int)num;
r[1] = num - r[0];
return r;
}
static void btn_cb(uint32_t button_states, uint32_t has_changed)
{
if (has_changed & button_states & DK_BTN1_MSK) {
// printk("\nBtn1\n");
int err = sps30_init(die_dev, &sps30);
if (err)
{
printk("Error: Could not initialize UART");
}
}
else if (has_changed & button_states & DK_BTN2_MSK)
{
// printk("\nBtn2\n");
int err = sps30_particle_read(die_dev);
if (err)
{
printk("Error: could not read measurement");
}
int *r = dec(sps30.nc_2p5);
printk("nc_2p5 = %d %d", r[0], r[1]);
r = dec(sps30.nc_10p0);
printk("nc_10 = %d %d", r[0], r[1]);
r = dec(sps30.typ_size);
printk("typ_size = %d %d", r[0], r[1]);
}
}
// Initalize buttons library and wait for button state change
void main(void) {
// Configure device
die_dev = device_get_binding(DT_LABEL(DT_NODELABEL(i2c2)));
if (!die_dev)
{
printk("No device found.");
}
int err = dk_buttons_init(btn_cb);
if (err < 0)
{
printk("Error: could not initalize button 1");
}
// Wait for input
while (1) {
k_cpu_idle();
}
}
Driver library: (c file)
#include "sps30_I2C.h"
#include <sys/printk.h>
#include <logging/log.h>
LOG_MODULE_REGISTER(sps30, CONFIG_APP_LOG_LEVEL);
const struct device *dev;
struct sps30_data sps30;
static uint8_t check(uint8_t data[2])
{
uint8_t crc = 0xFF;
for (int i = 0; i < 2; i++)
{
crc ^= data[i];
for (uint8_t bit = 8; bit > 0; --bit)
{
if (crc & 0x80)
{
crc = (crc << 1) ^ 0x31u;
}
else
{
crc = (crc << 1);
}
}
}
return crc;
}
// ---- I2C set pointer function ---- //
static int sps30_set_pointer(const struct device *dev, uint16_t ptr)
{
// Pointer MSB and LSB
// Note: LSB = ptr: 000000000 XXXXXXXX
// & 0xFF: 11111111
// = XXXXXXXX
unsigned char p[2] = {ptr >> 8, ptr & 0xFF};
if (i2c_write(dev, p, sizeof(p), SPS30_I2C_ADDRESS))
{
printk("\nError: could not set pointer\n");
return 1;
}
return 0;
}
// -- I2C set pointer read function -- //
static int sps30_set_pointer_read(const struct device *dev, uint16_t ptr, uint8_t *data)
{
int err = sps30_set_pointer(dev, ptr);
if (err)
{
return err;
}
if (i2c_read(dev, data, sizeof(data), SPS30_I2C_ADDRESS))
{
return 1;
}
return 0;
}
// -- i2c write function -- //
static int sps30_set_pointer_write(const struct device *dev, uint16_t ptr, uint8_t *wr_data)
{
unsigned char data[5];
// address pointer
data[0] = ptr >> 8;
data[1] = ptr & 0xFF;
for (int i = 2; i < sizeof(wr_data); i++)
{
data[i] = wr_data[i-2];
}
if (i2c_write(dev, data, sizeof(data), SPS30_I2C_ADDRESS))
{
printk("\nError: couln't write data\n");
return 1;
}
return 0;
}
/**
@brief Read particle measurment
@param dev Pointer to user device
@param sps30_data Pointer to sensor struct
@retval 0 if successful, 1 if errors occured
*/
int sps30_particle_read(const struct device *dev)
{
uint8_t rx_buf[60];
// ---- Wake up sequence ---- //
(void)sps30_set_pointer(dev, SPS_CMD_WAKE_UP);
int ret = sps30_set_pointer(dev, SPS_CMD_WAKE_UP);
if (ret) {
return ret;
}
// ---- Start measurement ---- //
uint8_t args_buf[2];
uint8_t start_buf[3];
args_buf[0] = 0x03; // set arg big endian float values
args_buf[1] = 0x00; // dummy byte
start_buf[0] = args_buf[0];
start_buf[1] = args_buf[1];
start_buf[2] = check(args_buf); // checksum byte
ret = sps30_set_pointer_write(dev, SPS_CMD_START_MEASUREMENT, start_buf); // start measurement with arg
if (ret)
{
printk("\nError: Failed starting measurement\n");
return ret;
}
// ---- read data ready flag ---- //
uint8_t flag_buf[3];
// flag_buf[0] = 0x00; flag_buf[1] Data ready flag byte, 0x00: no new measurements, 0x01: new measurements to read; flag_buf[3]: checksum
ret = sps30_set_pointer_read(dev, SPS_CMD_START_STET_DATA_READY, flag_buf);
if (ret)
{
printk("\nError: Failed to read data ready flag\n");
return ret;
}
// Read measured values (60 bytes)
while (flag_buf[1] == 0x00) { // Wait for data-ready flag
}
ret = sps30_set_pointer_read(dev, SPS_CMD_READ_MEASUREMENT, rx_buf);
if (ret)
{
printk("\nFailed to read measurement\n");
return ret;
}
sps30.nc_2p5 = (rx_buf[36] << 24) | (rx_buf[37] << 16) | (rx_buf[39] << 8) | rx_buf[40];
sps30.nc_10p0 = (rx_buf[48] << 24) | (rx_buf[49] << 16) | (rx_buf[51] << 8) | rx_buf[52];
sps30.typ_size = (rx_buf[54] << 24) | (rx_buf[55] << 16) | (rx_buf[57] << 8) | rx_buf[58];
// LOG_DBG("nc_2p5 = %d", drv_data->nc_2p5);
// LOG_DBG("nc_10 = %d", drv_data->nc_10p0);
// LOG_DBG("typ_siz = %d", drv_data->typ_siz);
// Stop measurment
ret = sps30_set_pointer(dev, SPS_CMD_STOP_MEASUREMENT);
if (ret)
{
printk("\nError: Failed to stop measurement\n");
return ret;
}
// Sleep mode
ret = sps30_set_pointer(dev, SPS_CMD_SLEEP);
if (ret)
{
printk("\nError: Failed to set device to sleep\n");
return ret;
}
return 0;
}
/**
@brief Initialize sps30 library
@param dev Pointer to user device
@retval 0 if successful, 1 if errors occured
*/
int sps30_init(const struct device *dev, struct sps30_data *data)
{
sps30 = *data; // Make user struct global
// nRF I2C master configuration
uint32_t i2c_cfg = I2C_SPEED_SET(I2C_SPEED_STANDARD) | I2C_MODE_MASTER;
uint8_t id[6];
if (i2c_configure(dev, i2c_cfg))
{
printk("\nError: could not configure i2c service\n");
}
//-- device status register --//
// int ret = sps30_set_pointer_read(dev, SPS_CMD_READ_DEVICE_STATUS_REG, id);
// if (ret)
// {
// printk("\nError: Reading device status register\n");
// return ret;
// }
// uint32_t id32 = (id[0] << 24) | (id[1] << 16) | (id[3] << 8) | id[4];
// // bit 21 fan speed
// if (id32 & SPS30_DEVICE_STATUS_FAN_ERROR_MASK)
// {
// printk("\nError: Fan speed out of range\n");
// return -EIO;
// }
// // bit 5 laser failure
// if (id32 & SPS30_DEVICE_STATUS_LASER_ERROR_MASK)
// {
// printk("\nError: Laser failure\n");
// return -EIO;
// }
// // bit 4 fan failure
// if (id32 & SPS30_DEVICE_STATUS_FAN_SPEED_WARNING)
// {
// printk("\nError: Fan failure, fan is mechanically blocked or broken\n");
// return -EIO;
// }
return 0;
}
(header file)
#ifndef sps30_h
#define sps30_h
#include <zephyr.h>
#include <device.h>
#include <stdio.h>
#include <stdlib.h>
#include <drivers/i2c.h>
#define SPS_CMD_START_MEASUREMENT 0x0010
#define SPS_CMD_START_MEASUREMENT_ARG 0x0300
#define SPS_CMD_STOP_MEASUREMENT 0x0104
#define SPS_CMD_READ_MEASUREMENT 0x0300
#define SPS_CMD_START_STET_DATA_READY 0x0202
#define SPS_CMD_AOP_DELAY_USEC 20000
#define SPS_CMD_GUTOCLEAN_INTERVAL 0x8004
#define SPS_CMD_GET_FIRMWARE_VERSION 0xd100
#define SPS_CMD_GET_SERIAL 0xd033
#define SPS_CMD_RESET 0xd304
#define SPS_CMD_SLEEP 0x1001
#define SPS_CMD_READ_DEVICE_STATUS_REG 0xd206
#define SPS_CMD_START_MANUAL_FAN_CLEANING 0x5607
#define SPS_CMD_WAKE_UP 0x1103
#define SPS_CMD_DELAY_USEC 5000
#define SPS_CMD_DELAY_WRITE_FLASH_USEC 20000
#define SPS30_I2C_ADDRESS 0x69
#define SPS30_MAX_SERIAL_LEN 32
/** The fan is switched on but not running */
#define SPS30_DEVICE_STATUS_FAN_ERROR_MASK (1 << 4)
/** The laser current is out of range */
#define SPS30_DEVICE_STATUS_LASER_ERROR_MASK (1 << 5)
/** The fan speed is out of range */
#define SPS30_DEVICE_STATUS_FAN_SPEED_WARNING (1 << 21)
struct sps30_data
{
float nc_2p5; // Number concentration PM2.5 [#/cm^3]
float nc_10p0; // Number concentration PM10 [#/cm^3]
float typ_size; // Typical Particle Size [nm]
};
/**
@brief Initialize sps30 library
@param dev Pointer to user device
@retval 0 if successful, 1 if errors occured
*/
int sps30_init(const struct device *dev, struct sps30_data *data);
/**
@brief Read particle measurment
@param dev Pointer to user device
@param sps30_data Pointer to sensor struct
@retval 0 if successful, 1 if errors occured
*/
int sps30_particle_read(const struct device *dev);
#endif
prj.conf
CONFIG_NEWLIB_LIBC=n # Serial CONFIG_SERIAL=y CONFIG_PRINTK=y # CONFIG_BOOT_BANNER=n # I2C CONFIG_I2C=y # Log CONFIG_LOG=y CONFIG_APP_LOG_LEVEL_DBG=y # DK settings CONFIG_DK_LIBRARY=y CONFIG_UI_BUTTON=y CONFIG_UI_BUZZER=n CONFIG_UI_LED_USE_PWM=n CONFIG_DK_LIBRARY_INVERT_LEDS=n
I have the "sps30_init" function tied to button 1 on the DK, while the "sps30_particle_read" tied to button 2.
Tried running it with the LTE Link Monitor to see the output from the serial terminal, but I am only getting "could not set pointer" and "Failed to read measurment" errors.
Any idea what could be wrong? My suspicion is that it's either a wrong config, or I'm using the library wrong, or the data format sent to the sensor is wrong.
As always, really appreciate the help :)