This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Problem with I2C driver - nRF9160DK

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

Related