Processor Expert SPI driver issue

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

Processor Expert SPI driver issue

1,779 Views
sudeepchandrase
Contributor II

MCU: MK20DX256

Code warrior: 10.6

I am using processor expert to generate SPI driver for our project. The SPI driver is used to communicate with a flash chip. The chip select of the flash chip is pulled high(active low) when a transaction(all bytes are sent) with the flash is over. We are using the 'OnBlockSent' event by the PE generated code to disable the chip select pin. We have observed that the 'OnBlockSent' event is generated before all bytes are transmitted in the SPI line. We tried diving into the generated code to see if any thing is wrong, we observed that the 'OnBlockSent ' is immediately called after the last byte of the requested sent block is put to the H/W buffer. This means when the 'OnBlockSent' event is called there could be one byte in the SPI PISO to be transmitted and another pending byte in the hardware buffer(our hardware buffer size is 1).  Below is the H/W buffer configuration:

pastedImage_1.png

Below is the ISR implementation where we observed the behavior:

PE_ISR(SM1_Interrupt)
{
/* {Default RTOS Adapter} ISR parameter is passed through the global variable */
SM1_TDeviceDataPtr DeviceDataPrv = INT_SPI1__DEFAULT_RTOS_ISRPARAM;
uint32_t StatReg = SPI_PDD_GetInterruptFlags(SPI1_BASE_PTR); /* Read status register */
uint16_t Data; /* Temporary variable for data */

if ((StatReg & SPI_PDD_RX_FIFO_OVERFLOW_INT) != 0U) { /* Is any error flag set? */
SPI_PDD_ClearInterruptFlags(SPI1_BASE_PTR,SPI_PDD_RX_FIFO_OVERFLOW_INT); /* Clear error flags */
}
if ((StatReg & SPI_PDD_RX_FIFO_DRAIN_INT_DMA) != 0U) { /* Is any char in HW buffer? */
Data = (uint16_t)SPI_PDD_ReadPopRxFIFOReg(SPI1_BASE_PTR); /* Read character from receiver */
SPI_PDD_ClearInterruptFlags(SPI1_BASE_PTR,SPI_PDD_RX_FIFO_DRAIN_INT_DMA); /* Clear Rx FIFO drain flags */
if (DeviceDataPrv->InpDataNumReq != 0x00U) { /* Is the receive block operation pending? */
*(DeviceDataPrv->InpDataPtr++) = (uint8_t)Data; /* Put a character to the receive buffer and increment pointer to receive buffer */
DeviceDataPrv->InpRecvDataNum++; /* Increment received char. counter */
if (DeviceDataPrv->InpRecvDataNum == DeviceDataPrv->InpDataNumReq) { /* Is the requested number of characters received? */
DeviceDataPrv->InpDataNumReq = 0x00U; /* If yes then clear number of requested characters to be received. */
SM1_OnBlockReceived(DeviceDataPrv->UserData);
}
}
}
if ((StatReg & SPI_PDD_TX_FIFO_FILL_INT_DMA) != 0U) { /* Is HW buffer empty? */
if (DeviceDataPrv->OutSentDataNum < DeviceDataPrv->OutDataNumReq) { /* Is number of sent characters less than the number of requested incoming characters? */
DeviceDataPrv->OutSentDataNum++; /* Increment the counter of sent characters. */
SPI_PDD_WriteMasterPushTxFIFOReg(SPI1_BASE_PTR, (uint32_t)(*((uint8_t *)DeviceDataPrv->OutDataPtr++) | DeviceDataPrv->TxCommand)); /* Put a character with command to the transmit register and increment pointer to the transmitt buffer */
if (DeviceDataPrv->OutSentDataNum == DeviceDataPrv->OutDataNumReq) {
DeviceDataPrv->OutDataNumReq = 0x00U; /* Clear the counter of characters to be send by SendBlock() */
SM1_OnBlockSent(DeviceDataPrv->UserData);
}
} else {
SPI_PDD_DisableDmasInterrupts(SPI1_BASE_PTR, SPI_PDD_TX_FIFO_FILL_INT_DMA); /* Disable TX interrupt */
}
SPI_PDD_ClearInterruptFlags(SPI1_BASE_PTR,SPI_PDD_TX_FIFO_FILL_INT_DMA); /* Clear Tx FIFO fill flags */
}
}

Question:

1. Isn't this implementation wrong, shouldn't 'OnBlockSent' be called after all bytes are put to the SPI line ?

2. What is the way we could figure out all SPI bytes are transmitted onto the SPI bus ?

0 Kudos
7 Replies

1,377 Views
fractalgarden
Contributor III

We came up to exactly the same situation and conclusions as reported by sudeepchandrase.
OnBlockSent is incorrectly raised when the last byte is loaded into the tx fifo register (last call of SPI_PDD_WriteMasterPushTxFIFOReg), not when the transmission on SOUT is finished.
This can be verified with oscilloscope by pulling chipselect high inside OnBlockSent, and it happens short after the beginning of the last byte, not after the end.
This implementation is certainly wrong, and it provides no way to check for the true end of transmission, since even the other methods (GetBlockSentStatus, GetSentDataNum) are derived from the same error.
I suggest to move the following code:

      if (DeviceDataPrv->OutSentDataNum == DeviceDataPrv->OutDataNumReq) {
        DeviceDataPrv->OutDataNumReq = 0x00U; /* Clear the counter of characters to be send by SendBlock() */
        DeviceDataPrv->SerFlag |= BLOCK_SENT; /* Set data block sent flag */
        SM1_OnBlockSent(DeviceDataPrv->UserData);
      }

down in a separate section that checks for TCF (SPI_PDD_TRANSFER_COMPLETE_INT):

  if ((StatReg & SPI_PDD_TRANSFER_COMPLETE_INT) != 0U) {
      if (DeviceDataPrv->OutSentDataNum == DeviceDataPrv->OutDataNumReq) {
        DeviceDataPrv->OutDataNumReq = 0x00U; /* Clear the counter of characters to be send by SendBlock() */
        DeviceDataPrv->SerFlag |= BLOCK_SENT; /* Set data block sent flag */
        SM1_OnBlockSent(DeviceDataPrv->UserData);
      }
      SPI_PDD_ClearInterruptFlags(SPI2_BASE_PTR,SPI_PDD_TRANSFER_COMPLETE_INT); /* Clear TCF flag */
  }

Hope this helps

0 Kudos

1,554 Views
Alexis_A
NXP TechSupport
NXP TechSupport

Dear Sudeep,

As far as I understand. this function put the data in the FIFO.

SPI_PDD_WriteMasterPushTxFIFOReg(SPI1_BASE_PTR, (uint32_t)(*((uint8_t *)DeviceDataPrv->OutDataPtr++) | DeviceDataPrv->TxCommand)); /* Put a character with command to the transmit register and increment pointer to the transmitt buffer */

This will be done many times until this condition is met:

if (DeviceDataPrv->OutSentDataNum == DeviceDataPrv->OutDataNumReq) {

And after that the OnBlockSent will be done.

The best way to test this will be set an slave that receive the data or use a logic analizer to check the transmission.

I hope this helps you.

Best Regards,

Alexis Andalon

0 Kudos

1,554 Views
sudeepchandrase
Contributor II

Thanks Alexis. We have already done this experiment and we see that OnBlockSent is called before the entire transmission is over.

My Hypothesis for the issue:

This is evident from the code as well. Ideally the OnBlockSent should be called when the last byte is emptied onto the MOSI line from the parallel in serial-out SPI register(SPI Tx register) which is not the case in the processor expert implementation. The "DeviceDataPrv->OutSentDataNum" is incremented when a byte is put in the hardware FIFO(which should have been ideally done when the byte got transmitted) and OnBlockSent is called when all the bytes are put onto the FIFO(ideally this should have been called after all bytes are put in the MOSI line). The onBlockSent event is generated on HW Tx FIFO empty and not when actual transmission complete. Isn't this implementation wrong ?

0 Kudos

1,554 Views
Alexis_A
NXP TechSupport
NXP TechSupport

Dear Sudeep,

 

I think you're misunderstanding the function of the SPI_PDD_WriteMasterPushTxFIFOReg function. This function is used to transmit the bytes. After called this function you can assume the byte will be transmitted automatically. So after the last byte is pushed in the FIFO, the OnBlockSent event is called to let know the transmission is completed.

 

Best Regards,

Alexis Andalon

0 Kudos

1,554 Views
prajwal_bv
Contributor I

Hello Alexis,

The FIFO Hardware buffer for Tx/ Rx and the Shift Register are different entities. I am attaching the users manual for your reference. The microcontroller has the following architecture:-

Capture.PNG

"SPI_PDD_WriteMasterPushTxFIFOReg" function is used to push a byte of data into the FIFO register upon the invocation of "Tx FIFO not Full Interrupt".

"if ((StatReg & SPI_PDD_TX_FIFO_FILL_INT_DMA) != 0U)" in the code is used to check if the Tx Buffer is not full. When the buffer is not full a byte of data is pushed by copying from a pointer to a bytearray in RAM to this Tx buffer using "SPI_PDD_WriteMasterPushTxFIFOReg".

You can verify this by looking into the macro definition of SPI_PDD_TX_FIFO_FILL_INT_DMA  which is equal to 0x2000000 which masks the 25th bit in the status register. Please refer to the attached image on the left. SR.PNG

The data is then transferred from the Tx Buffer to the shift register from which the data is transmitted over the MOSI pin of the controller. When the shift register transfers the data, the TCF (Transmission complete Flag) is set and the TXNXTPTR gets updated which points to the next byte of data in FIFO Tx buffer that needs to be placed in shift register.

The processor Expert configures the interrupt only for TFFF flag and not for TCF flag, so the call back "SM1_OnBlockSent" event happens while there is still a residual data in Tx FIFO buffer. Please have a look into the send script implemented by processor Expert:-

SM1_SendBlock.PNG

So if we are transmitting 3 bytes of data, the Tx FIFO not full interrupt is configured so in the first interrupt we push 1 byte of data to Tx FIFO buffer and we increment number of bytes sent by 1, Tx FIFO buffer internally puts this 1 byte of data to the Shift register and the Tx FIFO not full interrupt is triggered again, now a second byte of data is placed into the Tx FIFO buffer and the number of bytes sent is now 2. After the shift register transmits the 1st byte, Tx FIFO puts second byte of data into the Shift register and Tx FIFO causes another interrupt upon which 3rd byte of data is pushed into Tx FIFO buffer and the number of bytes sent is incremented to 3. At this stage Number of bytes sent is equal to the number of bytes requested to be sent so the PE_ISR function calls back the SM1_OnBlockSent function in events.c. But this is not expected because there is a byte of data in the Tx FIFO buffer and another byte of data which is in transmission state in the shift register.


Interrupt_Modes.PNGSo the TCF Flag have to be used to increment the number of bytes sent to mark the completion of the transmission and not the TFFF Flag. The image below shows list of options available to configure the interrupt but the processor expert has no options to configure the interrupt in all of these options, it will configure it by default for TFFF Flag. 

The major problem arises because the event SM1_OnBlockSent is used for (de)asserting CS pin to mark the end of transmission. The calling of SM1_OnBlockSent is based upon counting number of bytes put into the Tx FIFO buffer so (de)asserting CS pin would cause unexpected communication errors.

Cheers,

Prajwal B V

0 Kudos

1,554 Views
Alexis_A
NXP TechSupport
NXP TechSupport

Dear Prajwal,

The first time the interruption is called, one byte was already sent, if you check the byte that is send each time, when DeviceDataPrv->OutSentDataNum == DeviceDataPrv->OutDataNumReq the DeviceDataPrv->OutDataPtr was already clear.

The last time the interruption is called, all the data was already sent and only one dummy byte is sent in the last transmission.

I hope this helps you.

Best Regards,

Alexis Andalon

0 Kudos

1,554 Views
prajwal_bv
Contributor I

Hi Alexis,

The claim you made is as following:-

When the first time interruption is called, one byte is already sent.

It is incorrect because we have verified it while debugging by putting a breakpoint inside the interrupt, we found that when the first interrupt happens the following command is used to push 1st byte of data into FIFO buffer, not the second byte as per the claim.

SPI_PDD_WriteMasterPushTxFIFOReg(SPI1_BASE_PTR, (uint32_t) ( * ( (uint8_t *) DeviceDataPrv->OutDataPtr++) | DeviceDataPrv->TxCommand) );

But let us try to assume the claim is true and logically try to verify w.r.t to the configuration and the actual code generated by processor expert:-

FIFO_BLock.png

I think you haven't understood my previous reply about the interrupt. So I explaining it again here, the interrupt that is configured by processor expert is not the transmission complete interrupt but it is the buffer not full interrupt. This can be verified from the Flag mask that is used to configure and check the interrupt status in the code:-

The value of SPI_PDD_TX_FIFO_FILL_INT_DMA is  0x02000000 which is equal to 0b00000010000000000000000000000000 in binary, so it is used to mask the 25th bit 

32313029282726252423222120191817161514131211109876543210
000000010000000000000000000000000

Now consider the Interrupt Status Register, the 25th bit is mapped to TFFF Flag which is raised once the FIFO buffer is empty.

SR_Reg1.png

We can also see why the claim is false by logically verifying the code:-

Lets assume that the claim is true, so when the first time the interruption is called for transmitting data, the execution happens in the marked code snippet shown below.

pastedImage_28.png

Lets say we have 2 bytes of data to be transmitted using SPI which is {0x01, 0x02} for analysys:-

Step 1:- The code executes only if this line is true, since the assumption is that the first byte was transmitted, the buffer is not full and the if condition is true

if (StatReg & SPI_PDD_TX_FIFO_FILL_INT_DMA) ! = 0U) 

Step 2:- The next condition is also true since only 1 byte is transmitted, i.e the byte transmitted is lesser than bytes requested to transmit.

if (DeviceDataPrv->OutSentDataNum < DeviceDataPrv->OutDataNumReq)

Step 3:- In this line the variable that keeps track of the number of bytes that is sent gets incremented, so now:-

DeviceDataPrv->OutSentDataNum++
  • since the claim was 1 byte was already sent (which is 0x01), this gets incremented to value 2 which is wrong because it should have incremented after the transmission according to the claim.
DeviceDataPrv->OutSentDataNum++

Step4:- The command is supposed to push a byte of data from the array pointer to the FIFO Register.

SPI_PDD_WriteMasterPushTxFIFOReg(SPI1_BASE_PTR, (uint32_t) ( * ( (uint8_t *) DeviceDataPrv->OutDataPtr++) | DeviceDataPrv->TxCommand) );
  • So if the claim is 1 byte of data is already transmitted in first interrupt, then how did the pointer got incremented to point to the second byte? So again there is a problem. Lets say it somehow got incremented to point to second data, now when it is incremented again, it will be pointing to the 3rd byte.
  • Previously in Step 3 we assumed that OutSentDataNum it is incremented to 1 from 0 and because of this in the next interrupt Step 1 is true, Step 2 is also true, Step 3 OutSentDataNum  is incremented to 2 and in Step 4 we are trying to access the 3rd byte and so the system will throw Bus Fault.

What is the explanation for the issues that I have mentioned in Red with that claim.

Regards,

Prajwal B V

0 Kudos