Interfacing between MAX86141 and nRF5340dk

Hello,

I'm currently working on using a MAX86141 sensor (datasheet found here), and I've been running into some issues with communicating with it since this is the first time I've used SPI communication before. I've gone through the Dev Academy course and have been able to get some basic functionality down. For instance, I'm currently able to read from the part ID register (register 0xFF) and get back the expected value (0x25) and print this to a serial monitor. I'm still struggling, however, to actually functionally configure and read from the FIFO. I've been working on this for a while now and still haven't had any luck with it, so I'm hoping someone can help me find where I'm going wrong and point me in the right direction. 

Currently, I've set up the following read and write register commands

static int spi_read_reg(uint8_t reg, uint8_t values[], uint8_t size)
{
    int err;

    uint8_t tx_buffer[1];
    tx_buffer[0] = reg;
    
    struct spi_buf tx_spi_bufs[] = {
        { .buf = tx_buffer, .len = sizeof(tx_buffer)},
    };

    struct spi_buf_set spi_tx_buffer_set = {
        .buffers = tx_spi_bufs,
        .count = 1
    };

    struct spi_buf rx_spi_bufs[] = {
        {.buf = values, .len = size,}
    };

    struct spi_buf_set spi_rx_buffer_set = {
        .buffers = rx_spi_bufs,
        .count = 1
    };

    gpio_pin_set(gpio1_dev, GPIO_1_CS, 0);
    do{
        err=spi_write(spi_dev,&spi_cfg,&spi_tx_buffer_set);
        if (err < 0){break;}

        err=spi_read(spi_dev,&spi_cfg,&spi_rx_buffer_set);
    }while (false);
    gpio_pin_set(gpio1_dev, GPIO_1_CS, 1);

    uint8_t data = values[2];
    printk("Read: 0x%02X\n\r", data);

    if (err < 0)
        printk("Read Registers Failed");
}

static int spi_write_reg(uint8_t reg, uint8_t value)
{
	int err;

	uint8_t tx_buf[] = {(reg & 0x7F), value};
	struct spi_buf 		tx_spi_buf 		= {.buf = tx_buf, .len = sizeof(tx_buf)};
	struct spi_buf_set 	tx_spi_buf_set	= {.buffers = &tx_spi_buf, .count = 1};


    gpio_pin_set(gpio1_dev, GPIO_1_CS, 0);
    err=spi_write(spi_dev,&spi_cfg,&tx_spi_buf_set);
    gpio_pin_set(gpio1_dev, GPIO_1_CS, 1);

	if (err < 0) {
		printk("spi_write_dt() failed, err %d", err);
		return err;
	}

	return 0;
}

and I have the following definitions for my registers set up in the code:

#define WRITE_EN 0x00
#define READ_EN 0xFF

#define REG_INT_STAT_1         0x00      //Interrupt Status 1
#define REG_INT_STAT_2         0x01      //Interrupt Status 2
#define REG_INT_EN_1           0x02      //Interrupt Enable 1
#define REG_INT_EN_2           0x03      //Interrupt Enable 2
#define REG_FIFO_WR_PTR        0x04      //FIFO Buffer Write Pointer
#define REG_FIFO_RD_PTR        0x05      //FIFO Buffer Read Pointer
#define REG_OVF_COUNTER        0x06      //Over Flow Counter
#define REG_FIFO_DATA_COUNT    0x07      //FIFO Data Counter
#define REG_FIFO_DATA          0x08      //FIFO Data Register

#define REG_FIFO_CONFIG_1      0x09      //FIFO Configuration 1
#define REG_FIFO_CONFIG_2      0x0A      //FIFO Configuration 2
#define REG_MODE_CONFIG        0x0D      //System Control

//Photoplethysmogram (PPG) registers
#define REG_PPG_SYNC_CTRL      0x10      //PPG Sync Control
#define REG_PPG_CONFIG_1       0x11      //PPG Configuration Settings Group 1
#define REG_PPG_CONFIG_2       0x12      //PPG Configuration Settings Group 2
#define REG_PPG_CONFIG_3       0x13      //PPG Configuration Settings Group 3

#define REG_PROX_INTR_THRESH   0x14      //Prox Interrupt Threshold
#define REG_PD_BIAS            0x15      //Photo Diode Bias
#define REG_PICKET_FENCE       0x16      //Picket Fence Settings

#define REG_LED_SEQ_1          0x20      //LED Sequence 1
#define REG_LED_SEQ_2          0x21      //LED Sequence 2
#define REG_LED_SEQ_3          0x22      //LED Sequence 3

#define REG_LED1_PA            0x23      //LED 1 Pulse Amplitude
#define REG_LED2_PA            0x24     //LED 2 Pulse Amplitude
#define REG_LED3_PA            0x25      //LED 3 Pulse Amplitude
#define REG_LED4_PA            0x26      //LED 4 Pulse Amplitude
#define REG_LED5_PA            0x27      //LED 5 Pulse Amplitude
#define REG_LED6_PA            0x28      //LED 6 Pulse Amplitude
#define REG_LED_PILOT_PA       0x29      //LED Pilot Pulse Amplitude
#define REG_LED_RANGE_1        0x2A      //LED Amplitude Range 1
#define REG_LED_RANGE_2        0x2B      //LED Amplitude Range 2

//Hi resolution DAC settings for each LED.
#define REG_S1_HI_RES_DAC1     0x2C
#define REG_S2_HI_RES_DAC1     0x2D
#define REG_S3_HI_RES_DAC1     0x2E
#define REG_S4_HI_RES_DAC1     0x2F
#define REG_S5_HI_RES_DAC1     0x30
#define REG_S6_HI_RES_DAC1     0x31

#define REG_S1_HI_RES_DAC2     0x32
#define REG_S2_HI_RES_DAC2     0x33
#define REG_S3_HI_RES_DAC2     0x34
#define REG_S4_HI_RES_DAC2     0x35
#define REG_S5_HI_RES_DAC2     0x36
#define REG_S6_HI_RES_DAC2     0x37

//Die-temp registers
#define REG_TEMP_CONFIG        0x40
#define REG_TEMP_INTR          0x41
#define REG_TEMP_FRAC          0x42

//SHA256 registers
#define REG_SHA_CMD            0xF0
#define REG_SHA_CONFIG         0xF1

//Memory registers
#define REG_MEM_CTRL           0xF2
#define REG_MEM_IDX            0xF3
#define REG_MEM_DATA           0xF4
#define REG_PART_ID            0xFF

#define SIZE 			20
#define FIFO_SAMPLES 		128 //0x78

Page 20 of the datasheet gives the following pseudocode for initializing the optical AFE:

DEVICE OPEN
START;
// AFE Initialization
WRITE 0x1 to RESET[0]; // Soft Reset (Register 0x0D[0])
DELAY 1ms;
READ Interrupt Status 1; // Clear Interrupt (Register 0x00)
READ Interrupt Status 2; // Clear Interrupt (Register 0x01)
WRITE 0x1 to SHDN[0]; // Shutdown (Register 0x0D[1])
WRITE 0x3 to PPG_TINT[1:0]; // Pulse Width = 123.8ms (Register 0x11[1:0])
WRITE 0x2 to PPG1_ADC_RGE1:0]; // ADC Range = 16μA (Register 0x11[3:2])
WRITE 0x2 to PPG2_ADC_RGE1:0]; // ADC Range = 16μA (Register 0x11[3:2])
// For MAX86141 when used in Dual Channel only
WRITE 0x0 to SMP_AVE[2:0]; // Sample Averaging = 1 (Register 0x12[2:0])
WRITE 0x00 to PPG_SR[4:0]; // Sample Rate = 25sps (Register 0x12[7:3])
WRITE 0x3 to LED_SETLNG[1:0]; // LED Settling Time = 12ms (Register 0x13[7:6])
WRITE 0x01 to PDBIAS1[2:0]; // PD 1 Biasing for Cpd = 0~65pF (Register 0x15[2:0])
WRITE 0x01 to PDBIAS2[2:0]; // PD 1 Biasing for Cpd = 0~65pF (Register 0x15[2:0])
// For MAX86141 when used in Dual Channel only
WRITE 0x3 to LED1_RGE[1:0]; // LED Driver 1 Range = 124mA (Register 0x15[2:0])
WRITE 0x3 to LED2_RGE[1:0]; // LED Driver 2 Range = 124mA (Register 0x15[2:0])
WRITE 0x20 to LED1_DRV[1:0]; // LED 1 Drive Current = 15.36mA (Register 0x23[7:0])
WRITE 0x20 to LED2_DRV[1:0]; // LED 2 Drive Current = 15.36mA (Register 0x24[7:0])
WRITE 0x1 to LP_Mode[0]; // Low Power mode enabled
// FIFO Configuration
WRITE 0x10 to FIFO_A_FULL[6:0]; // FIFO INT triggered condition (Register 0x09[6:0])
WRITE 0x1 to FIFO_RO; // FIFO Roll Over enabled (Register 0x0A[1])
WRITE 0x1 to A_FULL_EN; // FIFO_A_FULL interrupt enabled (Register 0x02[7])
WRITE 0x1 to LEDC1[3:0]; // LED1 exposure configured in time slot 1
WRITE 0x2 to LEDC2[3:0]; // LED2 exposure configured in time slot 1
WRITE 0x0 to LEDC3[3:0];
WRITE 0x0 to LEDC4[3:0];
WRITE 0x0 to LEDC5[3:0];
WRITE 0x0 to LEDC6[3:0];
WRITE 0x0 to SHDN[0]; // Start Sampling STOP;
 

which I have tried to translate into the following code using my read and write register functions. I'm almost certain that something is wrong in this, but I'm not sure how else to interpret the pseudocode. (Note, I'm still not terribly confident on how to shift a value to write to specific bits in a register, so my attempt to do so may be completely off.)

void MAX86141_Initialize_AFE(void){
    uint8_t values[3];
    uint8_t size = 3;
    spi_write_reg(REG_MODE_CONFIG, 0x01);
    k_msleep(1);
    spi_write_reg(REG_MODE_CONFIG, (0x01<<1));
    spi_read_reg(REG_INT_STAT_1, values, size);
    spi_read_reg(REG_INT_STAT_2, values, size);
    spi_write_reg(REG_PPG_CONFIG_1, 0x2);
    spi_write_reg(REG_PPG_CONFIG_1, (0x2<<3));
    spi_write_reg(REG_PPG_CONFIG_1, (0x2<<3));
    spi_write_reg(REG_PPG_CONFIG_2, 0x0);
    spi_write_reg(REG_PPG_CONFIG_2, (0x0<<7));
    spi_write_reg(REG_PPG_CONFIG_2, 0x0);
    spi_write_reg(REG_PPG_CONFIG_3, (0x3<<7));
    spi_write_reg(REG_PD_BIAS, 0x01);
    spi_write_reg(REG_PD_BIAS,0x1);
    spi_write_reg(REG_PD_BIAS,0x3);
    spi_write_reg(REG_PD_BIAS,0x3);
    spi_write_reg(REG_PD_BIAS,0x3);
    spi_write_reg(REG_LED_SEQ_1, 0x20);
    spi_write_reg(REG_LED_SEQ_2, 0x20);
    spi_write_reg(REG_LED_SEQ_3, 0x00);
    spi_write_reg(REG_FIFO_CONFIG_1, 0xF);
    spi_write_reg(REG_FIFO_CONFIG_2, (0x1<<1));
    spi_write_reg(REG_INT_EN_1, (0x1<<7));
    spi_write_reg(REG_FIFO_CONFIG_1, 0xF);
    spi_write_reg(REG_LED_SEQ_1, 0x1);
    spi_write_reg(REG_LED_SEQ_1, 0x2<<4);
    spi_write_reg(REG_LED_SEQ_2, 0x0);
    spi_write_reg(REG_LED_SEQ_2, 0x0<<4);
    spi_write_reg(REG_LED_SEQ_3, 0x0);
    spi_write_reg(REG_LED_SEQ_3, 0x0<<4);
    spi_write_reg(REG_SHA_CMD, 0x0);
}

and the following pseudocode is on page 21 for reading from the FIFO after initialization:

#define FIFO_SAMPLES (128-0x10) // FIFO_A_FULL[6:0] = 0x10
void device_data_read(void) {
    int i;
    uint8_t sampleCnt;
    uint8_t dataBuf[FIFO_SAMPLES*3]; //(128 - FIFO_A_FULL[6:0]) samples, 3 byte/channel
    uint8_t tag1A[FIFO_SAMPLES/4]; //(128 - FIFO_A_FULL[6:0])/4channels samples
    uint8_t tag1B[FIFO_SAMPLES/4]; //(128 - FIFO_A_FULL[6:0])/4channels samples
    uint8_t tag2A [FIFO_SAMPLES/4]; //(128 - FIFO_A_FULL[6:0])/4channels samples
    uint8_t tag2B [FIFO_SAMPLES/4]; //(128 - FIFO_A_FULL[6:0])/4channels samples
    int led1A[FIFO_SAMPLES/4];
    int led1B[FIFO_SAMPLES/4];
    int led2A[FIFO_SAMPLES/4];
    int led2B[FIFO_SAMPLES/4];
    ReadReg(0x07, &sampleCnt); // sampleCnt should be the same value as FIFO_SAMPLES
    //Read FIFO ReadFifo(dataBuf, sampleCnt * 3);
    for ( i = 0; i < sampleCnt/4/*channels*/; i++ ) {
        tag1A[i] = (dataBuf[i*12+0] >> 3) & 0x1f;
        led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8) |
            dataBuf[i*12+2])) & 0x7ffff; // LED1, PD1
        tag1B[i] = (dataBuf[i*12+3] >> 3) & 0x1f;
        led1B[i] = ((dataBuf[i*12+3] << 16) | (dataBuf[i*12+4] << 8) |
            (dataBuf[i*12+5])) & 0x7ffff; // LED1, PD2
        tag2A[i] = (dataBuf[i*12+6] >> 3) & 0x1f;
        led2A[i] = ((dataBuf[i*12+6] << 16) | (dataBuf[i*12+7] << 8) |
            (dataBuf[i*12+8])) & 0x7ffff; // LED2, PD1
        tag2B[i] = (dataBuf[i*12+9] >> 3) & 0x1f;
        led2B[i] = ((dataBuf[i*12+9] << 16) | (dataBuf[i*12+10] << 8) |
            (dataBuf[i*12+11])) & 0x7ffff; // LED2, PD2 } }

which I have tried to translate to the following code:

void MAX86141_Read_FIFO(void){
    uint8_t values[3];
    uint8_t size = 3;
    int i;
    uint8_t sampleCnt[FIFO_SAMPLES];
    uint8_t sampleCnt3[FIFO_SAMPLES*3];
    uint8_t dataBuf[FIFO_SAMPLES*3];
    uint8_t tag1A[FIFO_SAMPLES/4];
    uint8_t tag1B[FIFO_SAMPLES/4]; 
    uint8_t tag2A[FIFO_SAMPLES/4];
    uint8_t tag2B[FIFO_SAMPLES/4];
    int led1A[FIFO_SAMPLES/4];
    int led1B[FIFO_SAMPLES/4];
    int led2A[FIFO_SAMPLES/4];
    int led2B[FIFO_SAMPLES/4];

    spi_read_reg(0x07, sampleCnt, sizeof(sampleCnt));
    spi_read_reg(dataBuf, sampleCnt3, sizeof(sampleCnt3));
    for ( i = 0; i < FIFO_SAMPLES/4; i++) {
        tag1A[i] = (dataBuf[i*12+0] >> 3) & 0x1f;
        led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8 | dataBuf[i*12+2])) & 0x7ffff; //LED1 PD1
        tag1B[i] = (dataBuf[i*12+3] >> 3) & 0x1f;
        led1B[i] = ((dataBuf[i*12+3] << 16) | (dataBuf[i*12+4] << 8 | dataBuf[i*12+5])) & 0x7ffff; //LED1 PD2
        tag2A[i] = (dataBuf[i*12+6] >> 3) & 0x1f;
        led2A[i] = ((dataBuf[i*12+6] << 16) | (dataBuf[i*12+7] << 8 | dataBuf[i*12+8])) & 0x7ffff; //LED1 PD2
        tag2B[i] = (dataBuf[i*12+9] >> 3) & 0x1f;
        led2B[i] = ((dataBuf[i*12+9] << 16) | (dataBuf[i*12+10] << 8 | dataBuf[i*12+11])) & 0x7ffff; //LED1 PD2
    }
}  

I know for sure that this code isn't correct since when I run it as is it can't run the program and keeps restarting on the serial monitor. Still, I've tried to change things around to no avail (I know that the issue seems to be the way the read register works not allowing for these inputs for the "values" and "size", since changing that will allow the program to run, albeit incorrectly). I just don't know what the issue is and how to fix it.

Ultimately all I'd like to do right now is turn on the LEDs and read the photodiodes connected to the sensor, and I can't seem to get either of those things to work. I currently have it physically set up with three LEDs and two photodiodes, but as long as I can get any of them functioning I should be able to work out how to get the combinations I need to work functioning. 

Thanks in advance!

Parents
  • Not sure if this will help, but there are a few simple code errors: The first is overflow leading to truncation

      spi_write_reg(REG_PPG_CONFIG_3, (0x3<<7)); <<==  truncation

    next is syntax

       led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8) | dataBuf[i*12+2])) & 0x7ffff; // LED1, PD1

    next is incorrect use of a buffer as a register:

        uint8_t dataBuf[FIFO_SAMPLES*3];
    
        spi_read_reg(0x07, sampleCnt, sizeof(sampleCnt));
        spi_read_reg(dataBuf, sampleCnt3, sizeof(sampleCnt3));

    Since the MAX86141 uses 24-bit transfers, there may be an issue with the design; worth fixing these issues first.

  • Thanks for the reply! To make sure that I understand correctly, to solve the truncation in that example I need to write 

      spi_write_reg(REG_PPG_CONFIG_3, (0x3<<6));

    to shift it the proper amount so 0x3 will be written on bits [7:6] as specified in the pseudocode, correct? If so, that makes sense and I'll make sure to check the code for other instances of that happening. I've made that correction in my code.

    For the syntax portion, I'm not sure what the right syntax is for this language. Should I write

       led1A[i] = {(dataBuf[i*12+0] << 16), (dataBuf[i*12+1] << 8), dataBuf[i*12+2])) & 0x7ffff}; // LED1, PD1

    instead? If so, I'll make that change and see if it helps anything.

    Lastly, I didn't even notice the issue with the buffer. Thanks for pointing that out! I believe I should be reading from register 0x08 for that line of code, so I've made that correction now to see if that helps. The code still isn't working correctly, but thanks for pointing those issues out!

  • Syntax: parenthesis in wrong place, correct line:

     led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8) | dataBuf[i*12+2]) & 0x7ffff; //LED1 PD1

  • Note all that group of lines in MAX86141_Read_FIFO() were incorrect (in case you didn't notice ..)

            tag1A[i] = (dataBuf[i*12+0] >> 3) & 0x1f;
            led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8) | dataBuf[i*12+2]) & 0x7ffff; //LED1 PD1
            tag1B[i] = (dataBuf[i*12+3] >> 3) & 0x1f;
            led1B[i] = ((dataBuf[i*12+3] << 16) | (dataBuf[i*12+4] << 8) | dataBuf[i*12+5]) & 0x7ffff; //LED1 PD2
            tag2A[i] = (dataBuf[i*12+6] >> 3) & 0x1f;
            led2A[i] = ((dataBuf[i*12+6] << 16) | (dataBuf[i*12+7] << 8) | dataBuf[i*12+8]) & 0x7ffff; //LED1 PD2
            tag2B[i] = (dataBuf[i*12+9] >> 3) & 0x1f;
            led2B[i] = ((dataBuf[i*12+9] << 16) | (dataBuf[i*12+10] << 8) | dataBuf[i*12+11]) & 0x7ffff; //LED1 PD2

Reply
  • Note all that group of lines in MAX86141_Read_FIFO() were incorrect (in case you didn't notice ..)

            tag1A[i] = (dataBuf[i*12+0] >> 3) & 0x1f;
            led1A[i] = ((dataBuf[i*12+0] << 16) | (dataBuf[i*12+1] << 8) | dataBuf[i*12+2]) & 0x7ffff; //LED1 PD1
            tag1B[i] = (dataBuf[i*12+3] >> 3) & 0x1f;
            led1B[i] = ((dataBuf[i*12+3] << 16) | (dataBuf[i*12+4] << 8) | dataBuf[i*12+5]) & 0x7ffff; //LED1 PD2
            tag2A[i] = (dataBuf[i*12+6] >> 3) & 0x1f;
            led2A[i] = ((dataBuf[i*12+6] << 16) | (dataBuf[i*12+7] << 8) | dataBuf[i*12+8]) & 0x7ffff; //LED1 PD2
            tag2B[i] = (dataBuf[i*12+9] >> 3) & 0x1f;
            led2B[i] = ((dataBuf[i*12+9] << 16) | (dataBuf[i*12+10] << 8) | dataBuf[i*12+11]) & 0x7ffff; //LED1 PD2

Children
No Data
Related