LPSPI driver bug - Bytes sent IN WRONG ORDER

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

LPSPI driver bug - Bytes sent IN WRONG ORDER

Jump to solution
2,420 Views
davenadler
Senior Contributor I

In most cases I'm not able to get LPSPI to work properly on RT1024.
Update: Problem was due to a nasty LPSPI driver bug explained below.
Best Regards, Dave

Background
4 different sensors on LPSPI bus 4. Trying to validate operation by reading chip IDs from each sensor.
One sensor (ND120) I can read from properly, so the pin configuration, clocks, hardware seem all OK.
The other 3 sensors I can only operate using "continuous mode" which is not what I need in this case.

Code
Here's the ND120 code that operates AOK:

 

typedef struct {
	// normal read is just 32-bit pressure and temperature
	int16_t pressure;
	int16_t temperature;
	// extended data read (beyond 4 bytes/32 bits) should get the following:
	char model[8];
	uint32_t serialNumber;
	char softwareVersion[6];
	char fill[2]; // because LPSPI API requires multiple of 4 bytes; don't know why...
} ND120_output_T;

typedef struct {
	uint8_t mode;
	uint8_t rate;
	// ToDo: ND120: ND120_control_T ctor to assemble fields of mode and rate
} ND120_control_T;

void test_ND120() {
	lpspi_master_config_t ND120_LPSPI_MasterConfig =
	{
		.baudRate = 150000U,                 	/*!< Baud Rate for LPSPI. 6uS cycle = 166,666Hz */
		.bitsPerFrame = 8 * sizeof(ND120_output_T),/*!< Bits per frame, minimum 8, maximum 4096.*/
		// Clock SPI mode '00' (CPOL = CPHA = 0)
		.cpol = kLPSPI_ClockPolarityActiveLow,	/*!< Clock polarity. */
		.cpha = kLPSPI_ClockPhaseSecondEdge,   	/*!< Clock phase. */
		.direction = kLPSPI_MsbFirst,			/*!< MSB or LSB data shift direction. */
		.pcsToSckDelayInNanoSec = 110000,		/*!< PCS to SCK delay time in nanoseconds, setting to 0 sets the minimum delay.
												     It sets the boundary value if out of range.
												     spec: 100us */
		.lastSckToPcsDelayInNanoSec = 20000, 		/*!< Last SCK to PCS delay time in nanoseconds, setting to 0 sets the minimum
												     delay. It sets the boundary value if out of range.
												     spec:  20us */
		.betweenTransferDelayInNanoSec = 0, 	/*!< After the SCK delay time with nanoseconds, setting to 0 sets the
												     minimum delay. It sets the boundary value if out of range.*/
		.whichPcs = kLPSPI_Pcs0,                /*!< Desired Peripheral Chip Select (PCS) - over-ridden in transfer API */
		.pcsActiveHighOrLow = kLPSPI_PcsActiveLow, /*!< Desired PCS active high or low */
		.pinCfg = kLPSPI_SdiInSdoOut,			/*!< Configures which pins are used for input and output data
												     during single bit transfers.*/
		.dataOutConfig = kLpspiDataOutRetained, /*!< Configures if the output data is tristated
												* between accesses (LPSPI_PCS is negated). */
/*guess*/.enableInputDelay = false, /*!< Enable master to sample the input data on a delayed SCK. This can help improve slave
								  setup time. Refer to device data sheet for specific time length. */

	};
	LPSPI_MasterInit(LPSPI4, &ND120_LPSPI_MasterConfig, BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT); // enables clock and LPSI module
	ND120_control_T ctrl = {
		.mode = 0,
		.rate = 0,
	};
	ND120_output_T result;
	memset(&result,0,sizeof(result));
	lpspi_transfer_t t1 =
	{
	    .txData = (uint8_t*)&ctrl,    /*!< Send buffer. */
	    .rxData = (uint8_t*)&result,  /*!< Receive buffer. */
	    .dataSize = sizeof(result),   /*!< Transfer bytes. */
	    /*!< Transfer transfer configuration flags. Set from _lpspi_transfer_config_flag_for_master if
	         the transfer is used for master or _lpspi_transfer_config_flag_for_slave enumeration if the
	    	 transfer is used for slave.*/
	    .configFlags = kLPSPI_MasterPcs0  /*| kLPSPI_MasterPcsContinuous */,
	};
	// Reset ND120
	GPIO_PinWrite(BOARD_INITPINS_ND120_RESET_GPIO, BOARD_INITPINS_ND120_RESET_PIN, 0);
	vTaskDelay(pdMS_TO_TICKS(10));
	GPIO_PinWrite(BOARD_INITPINS_ND120_RESET_GPIO, BOARD_INITPINS_ND120_RESET_PIN, 1);
	vTaskDelay(pdMS_TO_TICKS(100)); // wait for reset to actually complete
	// ND120 Returns:
	//   {pressure = 5988, temperature = 1, model = "21DN\0\0\00", serialNumber = 1163278597, softwareVersion = "7410\0", fill = "\0A"}
	status_t s1 = LPSPI_MasterTransferBlocking(LPSPI4, &t1);
	assert(s1 == kStatus_Success);
	(void)s1;
	bool connected = memcmp(result.model,"21DN",5)==0;
	assert(connected);
	(void)connected;
	// Can we do it twice?
	status_t s2 = LPSPI_MasterTransferBlocking(LPSPI4, &t1);
	assert(s2 == kStatus_Success);
	(void)s2;
	bool connected2 = memcmp(result.model,"21DN",5)==0;
	assert(connected2);
	(void)connected2;
}

 

Using 'continuous mode', I can read the device ID from the 3 BMP581 sensors:

 

void test_bmp581() {
    // first try, using 'continuous CS', works before or after ND120 test
    uint8_t t1_ReadChipID[2] = {
        0x80 /* read operation */ | /* register */ BMP5_REG_CHIP_ID,
        0 // dummy, time for data reply
    };
    uint8_t t1_result[sizeof(t1_ReadChipID)] = {55,66};
    lpspi_master_config_t masterConfig =
    {
        .baudRate = 10000000U,                 	/*!< Baud Rate for LPSPI. 10MHz (12.5MHz spec) */
        .bitsPerFrame = 8,                     	/*!< Bits per frame, minimum 8, maximum 4096.*/
                // Clock SPI mode '11' (CPOL = CPHA = 1)
        .cpol = kLPSPI_ClockPolarityActiveLow, 	/*!< Clock polarity. */
        .cpha = kLPSPI_ClockPhaseSecondEdge,   	/*!< Clock phase. */
        .direction = kLPSPI_MsbFirst,			/*!< MSB or LSB data shift direction. */
        /*guess*/.pcsToSckDelayInNanoSec = 50,	/*!< PCS to SCK delay time in nanoseconds, setting to 0 sets the minimum delay.
          It sets the boundary value if out of range.
          ToDo: T_setup_csb MISSING FROM SPECIFICATION. */
        .lastSckToPcsDelayInNanoSec = 50, 		/*!< Last SCK to PCS delay time in nanoseconds, setting to 0 sets the minimum
        delay. It sets the boundary value if out of range.
        T_hold_csb = 40ns spec.*/
        .betweenTransferDelayInNanoSec = 0, 	/*!< After the SCK delay time with nanoseconds, setting to 0 sets the
        minimum delay. It sets the boundary value if out of range.*/
        .whichPcs = kLPSPI_Pcs1,                /*!< Desired Peripheral Chip Select (PCS) - over-ridden in transfer API */
        .pcsActiveHighOrLow = kLPSPI_PcsActiveLow, /*!< Desired PCS active high or low */
        .pinCfg = kLPSPI_SdiInSdoOut,			/*!< Configures which pins are used for input and output data
        during single bit transfers.*/
        .dataOutConfig = kLpspiDataOutRetained, /*!< Configures if the output data is tristated
        * between accesses (LPSPI_PCS is negated). */
        /*guess*/.enableInputDelay = false, /*!< Enable master to sample the input data on a delayed SCK. This can help improve slave
          setup time. Refer to device data sheet for specific time length. */
    };
    LPSPI_MasterInit(LPSPI4, &masterConfig, BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT); // enables clock and LPSI module
    lpspi_transfer_t t1 =
    {
        .txData = &t1_ReadChipID[0],   /*!< Send buffer. */
        .rxData = &t1_result[0],       /*!< Receive buffer. */
        .dataSize = sizeof(t1_result), /*!< Transfer bytes. */
        /*!< Transfer transfer configuration flags. Set from _lpspi_transfer_config_flag_for_master if
          the transfer is used for master or _lpspi_transfer_config_flag_for_slave enumeration if the
          transfer is used for slave.*/
        .configFlags = kLPSPI_MasterPcs1 /* Overridden below anyway: | kLPSPI_MasterPcsContinuous */ ,
    };
    // trying to read multiple devices fails, probably because we've got CS confusion from 'continuous' mode
    // ===================  PCS #1  =======================
    t1.configFlags = kLPSPI_MasterPcs1  | kLPSPI_MasterPcsContinuous ;
    status_t s1 = LPSPI_MasterTransferBlocking(LPSPI4, &t1);
    assert(s1 == kStatus_Success);
    printf("BMP581-1 id (should be x50): x%x\n", t1_result[1]);
    assert(t1_result[1] == 0x50);
}

 

If I try without continuous mode (which is what's needed here), I get blank results:

 

void test_bmp581() {
    uint8_t t1_ReadChipID[2] = {
        0x80 /* read operation */ | /* register */ BMP5_REG_CHIP_ID,
        0 // dummy, time for data reply
    };
    uint8_t t1_result[sizeof(t1_ReadChipID)] = {55,66};
    lpspi_master_config_t masterConfig =
    {
        .baudRate = 10000000U,                 	/*!< Baud Rate for LPSPI. 10MHz (12.5MHz spec) */
        .bitsPerFrame = 8 * sizeof(t1_result), 	/*!< Bits per frame, minimum 8, maximum 4096.*/
        // Clock SPI mode '11' (CPOL = CPHA = 1)
        .cpol = kLPSPI_ClockPolarityActiveLow, 	/*!< Clock polarity. */
        .cpha = kLPSPI_ClockPhaseSecondEdge,   	/*!< Clock phase. */
        .direction = kLPSPI_MsbFirst,			/*!< MSB or LSB data shift direction. */
        /*guess*/.pcsToSckDelayInNanoSec = 50,  /*!< PCS to SCK delay time in nanoseconds, setting to 0 sets the minimum delay.
                                                     It sets the boundary value if out of range.
                                                     ToDo: T_setup_csb MISSING FROM BOSCH SPECIFICATION. */
        .lastSckToPcsDelayInNanoSec = 50, 		/*!< Last SCK to PCS delay time in nanoseconds, setting to 0 sets the minimum
                                                     delay. It sets the boundary value if out of range.
                                                     T_hold_csb = 40ns spec.*/
        .betweenTransferDelayInNanoSec = 0, 	/*!< After the SCK delay time with nanoseconds, setting to 0 sets the
                                                     minimum delay. It sets the boundary value if out of range.*/
        .whichPcs = kLPSPI_Pcs1,                /*!< Desired Peripheral Chip Select (PCS) - over-ridden in transfer API */
        .pcsActiveHighOrLow = kLPSPI_PcsActiveLow, /*!< Desired PCS active high or low */
        .pinCfg = kLPSPI_SdiInSdoOut,			/*!< Configures which pins are used for input and output data
        										     during single bit transfers.*/
        .dataOutConfig = kLpspiDataOutRetained, /*!< Configures if the output data is tristated
         	 	 	 	 	 	 	 	 	 	 *   between accesses (LPSPI_PCS is negated). */
        /*guess*/.enableInputDelay = false, /*!< Enable master to sample the input data on a delayed SCK. This can help improve slave
          setup time. Refer to device data sheet for specific time length. */
    };
    LPSPI_MasterInit(LPSPI4, &masterConfig, BOARD_BOOTCLOCKRUN_LPSPI_CLK_ROOT); // enables clock and LPSI module
    lpspi_transfer_t t1 =
    {
        .txData = &t1_ReadChipID[0],   /*!< Send buffer. */
        .rxData = &t1_result[0],       /*!< Receive buffer. */
        .dataSize = sizeof(t1_result), /*!< Transfer bytes. */
        /*!< Transfer transfer configuration flags. Set from _lpspi_transfer_config_flag_for_master if
          the transfer is used for master or _lpspi_transfer_config_flag_for_slave enumeration if the
          transfer is used for slave.*/
        .configFlags = kLPSPI_MasterPcs1 /* Overridden below anyway: | kLPSPI_MasterPcsContinuous */ ,
    };
    // ToDo: BMP581: Bug: reads 0x00 0x00
    // ===================  PCS #1  =======================
    t1.configFlags = kLPSPI_MasterPcs1  /*| kLPSPI_MasterPcsContinuous */;
    status_t s1 = LPSPI_MasterTransferBlocking(LPSPI4, &t1);
    assert(s1 == kStatus_Success);
    assert(t1_result[1] == 0x50); // <<< Fails here
}

 

Labels (1)
Tags (1)
1 Solution
2,368 Views
davenadler
Senior Contributor I

Had to break out the logic analyzer to find this one.
Here's an image of the correctly ordered bytes, as was shown in the "continuous CS" example
(though here I've de-asserted CS after the transfer):

CorrectByteOrdering.PNG

Using the normal (no continuous CS) mode, the LPSPI driver sends the bytes in the wrong order:

IncorrectByteOrdering.PNG

We know this is a driver bug in the normal (non-continuous CS mode) short transmission, because the driver:

  1. Sends the bytes in different order depending on the "CS continuous' option;
    obviously changing the CS option should have no effect on the order bytes are sent,
  2. Sends the bytes in the correct order for longer transmissions as in the ND120 case;
    obviously changing the number of bytes sent should not change the order, and
  3. Sends the bytes ordered inconsistently with other FSL SPI drivers (for example Kinetis).

It would be great if someone from NXP ( @EdwinHz ?) can:

  1. Confirm they have read and understood this, and
  2. Confirm they have submitted this as a bug to development.

I believe the bug is caused by incorrect interpretation of the isByteSwap parameter in the routine LPSPI_CombineWriteData.
If NXP has a repository accepting pull-requests, please let me know and I'll create a pull-request for the fix.

Thanks,
Best Regards, Dave

View solution in original post

5 Replies
2,369 Views
davenadler
Senior Contributor I

Had to break out the logic analyzer to find this one.
Here's an image of the correctly ordered bytes, as was shown in the "continuous CS" example
(though here I've de-asserted CS after the transfer):

CorrectByteOrdering.PNG

Using the normal (no continuous CS) mode, the LPSPI driver sends the bytes in the wrong order:

IncorrectByteOrdering.PNG

We know this is a driver bug in the normal (non-continuous CS mode) short transmission, because the driver:

  1. Sends the bytes in different order depending on the "CS continuous' option;
    obviously changing the CS option should have no effect on the order bytes are sent,
  2. Sends the bytes in the correct order for longer transmissions as in the ND120 case;
    obviously changing the number of bytes sent should not change the order, and
  3. Sends the bytes ordered inconsistently with other FSL SPI drivers (for example Kinetis).

It would be great if someone from NXP ( @EdwinHz ?) can:

  1. Confirm they have read and understood this, and
  2. Confirm they have submitted this as a bug to development.

I believe the bug is caused by incorrect interpretation of the isByteSwap parameter in the routine LPSPI_CombineWriteData.
If NXP has a repository accepting pull-requests, please let me know and I'll create a pull-request for the fix.

Thanks,
Best Regards, Dave

1,552 Views
davenadler
Senior Contributor I

This is getting a bit silly.
See: https://github.com/nxp-mcuxpresso/mcux-sdk/issues/113 

0 Kudos
Reply
2,048 Views
thomas_li
NXP Employee
NXP Employee
2,402 Views
Pavel_Hernandez
NXP TechSupport
NXP TechSupport

Hello, I recommend using the examples to analyze if you miss any configuration. 

The examples can download in the next URL Select Board | MCUXpresso SDK Builder (nxp.com)

Best regards,
Pavel

0 Kudos
Reply
2,400 Views
davenadler
Senior Contributor I

@Pavel_Hernandez- I have already done that (I used examples to create this code).
Can you find the error in my configuration?
Thanks!
Best Regards, Dave