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 :)