Hi,
I'm currently trying to utilize a MAX86141 sensor (datasheet found here) and am using an nRF5340 development kit to interface with it using SPI. The issue is, I've never used SPI before, so I could use some help setting this up and haven't been able to find much to help me along with it. Currently, I have the following code set up to simply read the part ID from register 0xFF and return 0x25, which is giving the expected result.
#include <zephyr/kernel.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/spi.h>
#include <ncs_version.h>
#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
#define RESET_REG 0x00
#define READ_CMD 0x07
//Mode 3
static const struct spi_config spi_cfg = {
.operation = SPI_OP_MODE_MASTER | SPI_WORD_SET(8) | SPI_TRANSFER_MSB |
SPI_MODE_CPOL | SPI_MODE_CPHA,
.frequency = 125000,
.slave = 0,
};
#define SPI1_NODE DT_NODELABEL(my_spi_master)
static const struct device *spi_dev = DEVICE_DT_GET(SPI1_NODE);
#define MY_GPIO1 DT_NODELABEL(gpio1) //CS
#define GPIO_1_CS 12
const struct device *gpio1_dev = DEVICE_DT_GET(MY_GPIO1);
void spi_test_send(uint8_t reg, uint8_t size)
{
int err;
uint8_t tx_buffer[1];
tx_buffer[0] = size | reg;
static uint8_t rx_buffer[3];
for (int i=0; i<sizeof(tx_buffer);i++)
{
rx_buffer[i] = 0x00;
}
const struct spi_buf tx_buf = {
.buf = tx_buffer,
.len = sizeof(tx_buffer)
};
const struct spi_buf_set tx = {
.buffers = &tx_buf,
.count = 1
};
struct spi_buf rx_buf = {
.buf = rx_buffer,
.len = size,
};
const struct spi_buf_set rx = {
.buffers = &rx_buf,
.count = 1
};
gpio_pin_set(gpio1_dev, GPIO_1_CS, 0);
err = spi_transceive(spi_dev, &spi_cfg, &tx, &rx);
gpio_pin_set(gpio1_dev, GPIO_1_CS, 1);
if (err) {
printk("SPI error: %d\n", err);
} else {
printk("\r\nTX sent: ");
printk("0x%02x", tx_buffer[0]);
printk("\r\nRX recv: ");
printk("0x%02x", rx_buffer[2]);
printk("\r\n");
}
}
void main(void)
{
gpio_pin_configure(gpio1_dev, GPIO_1_CS, GPIO_OUTPUT);
gpio_pin_set(gpio1_dev, GPIO_1_CS, 1);
while(1){
spi_test_send(WRITE_EN, REG_PART_ID);
k_sleep(K_MSEC(1000));
}
}
The problem is that now I need to actually utilize the sensor and get it to turn on the desired LEDs and read the photodiode values, and I've been struggling with where to even start with this. The datasheet provides the following pseudocode (Pages 21-22), but I don't know how to translate this into usable code that will actually function. Any help or direction would be greatly appreciated and thanks in advance!
Pseudo-Code Example of 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;
Example pseudo-code for reading data from FIFO when using dual photodiode channels and two LED channels.
#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 } }