cancel
Showing results for 
Search instead for 
Did you mean: 

24 bit SPI TransmitReceive oddity

Carl Farrington
Associate III

Hi. I think I have an idea why this does what it does, but wanted to ask advice anyway.

I am interfacing an STM32F767 over SPI with an SSD2828 parallel RGB to MIPI bridge.

It (amongst other options), uses what they describe as 24 bit SPI.

I am using the SPI peripheral in 8 bit mode, and it seems to work just fine.

The issue is, when using DMA, everything looks nice and the communication is all in one transaction.

When not using DMA, the SPI clock stops after 16 bits, and then after a delay picks up again.

I think it's because of the 16 bit buffer, and the software function having to empty the RX FIFO or whatever.

The annoying thing though, is that the recevied data is actually only 16 bits. The first byte is empty, but the HAL_TransmitReceive doesn't, erm, have a way of telling it to ignore the first byte on the rx side.

The communication protocol goes something like:

SPI Send: {73, 0 , 0

SPI Receive: {00, data, data]

so on the TX side we send the first byte to indicate that we want to receive data. So only 1 byte is send, and only 2 are received, in the overall 24-bit packet. Am I making any sense?

I suppose I was wondering, well, given that I'm a complete beginner with C/C++, and have relied on CubeMX to do all the DMA setup for me, well, I would rather not use DMA for this simple bit of LCD/bridge initialisation, because it's code I won't really understand.

Is it OK that the non-DMA SPI is broken up and the clock stops like it does? I seemed to get the correct response to that ID requests (it returned 0x28, 0x28 which is the chip ID).

I suppose it's a compromise given that HAL_SPI_TransmitReceive only allows you to give a single data size that is used for both tx and rx size, and that it can't be told to skip the first byte of the Receive side.

Here's my function:

void SSD2828_SPI_ReadReg24b3(uint8_t reg, uint8_t* data){
	//SSD2828's 24 bit 3 wire SPI mode. Set the STM32 peripheral for 8 bit mode, hardware NSS.
 
	 /* this is the initial register select, with the first byte identifying command, and write.
	  * you'd think it should be 0x71 to indicate read, but apparently not. We're just selecting the register
	  * next two bytes are the register. for 24 bit format it's MS-byte first in the data.
	  */
	uint8_t SPI24bitPkt1[3] = {0x70, 0x00, reg};
	HAL_SPI_Transmit(&hspi5, SPI24bitPkt1, 3, 100); // 
 
	uint8_t SPI24bitPkt2[3] = {0x73, 0, 0}; // yes this time we set the 1 for Read. The SSD2828 will start sending after the first byte.
	uint8_t receivedData[3];
	HAL_SPI_TransmitReceive(&hspi5, SPI24bitPkt2, receivedData, 3, 100);
	
	//now I want to lose that first byte and just return the actual data
	data[0]=receivedData[1];
	data[1]=receivedData[2];
}

And here's what I'm talking about.. the clean one is DMA, the other one is not DMA.

(I'm having trouble using the Image button here - I've just used Attach instead)

Also, totally unrelated, but there seem to be lots of people who don't realise that the Size parameter is one number for both rx & tx and that rx and tx have to be the same size. Loads of people on stackoverflow thinking it should be rx+tx (i.e. 6)

12 REPLIES 12
T J
Lead

flush your receiver before you transmit

T J
Lead

I use these routines, without DMA :(

void transfer(unsigned short data) {   // send only
    char RxSPI;
   
    while (!(hspi1.Instance->SR  & SPI_FLAG_TXE)) ;
    *((__IO uint8_t *)&hspi1.Instance->DR) =  data;
            RxSPI = hspi1.Instance->DR;// + readSPI_SR;    // read Rx byte and discard, only clearing the buffer
}
char transfer_receive(unsigned short data) {   // send and receive byte
 
    char RxSPI;                                                     
    while(!(hspi1.Instance->SR  & SPI_FLAG_TXE))    // make sure the last byte is gone.
         ;
    while ((hspi1.Instance->SR  & SPI_FLAG_RXNE))  
     RxSPI = hspi1.Instance->DR;      //empty and dump all fifo bytes
 
    *((__IO uint8_t *)&hspi1.Instance->DR) = data;  // force the SPI to transceive 8 bit
    while(!(hspi1.Instance->SR  & SPI_FLAG_TXE))    // wait to transmitter double buffer to shift into transmitter
         ;                   
    while ((hspi1.Instance->SR  & SPI_FLAG_BSY)) ;  // wait for data to shift out of the processor pins
    while((hspi1.Instance->SR  & SPI_FLAG_RXNE))   // load all the bytes received, only keep the last one
        RxSPI = hspi1.Instance->DR;     // we only want the last byte
 
    return RxSPI;
}

That function is written so that it uses the "data packing" feature, transmitting in 16-bit chunks until the last byte.

I wouldn't be surprised if the 3us delay is simply the time it takes the software to switch the FIFO threshold and to pick the last byte and put it into the Tx buffer. What is the optimization setting of the compiler? Where is this code run from?

Carl Farrington
Associate III

Hi @TJ (still learning this salesforce system.. can't reply to individual messages in context). Thanks for your code. That does just one byte at a time though. I'm sure if that function is run multiple times, wouldn't the hardware NSS revert in between uses, and the clock stop just like in my use of HAL_TransmitReceive ?

Carl Farrington
Associate III

@Community member​ I did a lot of playing around with the RXNE stuff. I was sure there was a bug in the HAL code for the rx fifo thresholds. There is certainly a bug in the comments that are with the code, but I think I eventually realised that it's just the comments and/or constant labels that are back to front. So you think the 3us could just be that it's breaking out of the SPI code to run the code to set the SPI rx fifo interrupt thingy (RXNE or whatever it was) to 8 bit so that.. well so that it gets the notification that the job's done?

The bottom line though is that without DMA, that 24 bit SPI transaction looks nothing like what my target chip's datasheet says I should be sending it. It does happen to work, at least for that minimal 'chip id' test, but, I'm just wondering is it good enough?

With DMA it's perfect.

T J
Lead

like this ?

char writeLis3DEByte(char address, char writeByte) {
    address &= 0x3F;     // 6bit address
    //address += 0x80;    // Write == 0
    address += 0x40;     // Auto Increment
    HAL_GPIO_WritePin(LIS3DE_nSS_GPIO_Port, LIS3DE_nSS_Pin, GPIO_PIN_RESET);
    
    transfer(address);
    char dummyByte = 0;
    char RxSPI = transfer_receive(writeByte);
    
    //putc1(RxSPI);
    HAL_GPIO_WritePin(LIS3DE_nSS_GPIO_Port, LIS3DE_nSS_Pin, GPIO_PIN_SET);
    return RxSPI;
   
}

Carl Farrington
Associate III

That's for this chip? Page 25, figure 8 ?

https://www.st.com/resource/en/datasheet/lis3de.pdf

I was just wondering if on a logic analyser or scope you would get an uninterrupted clock that's all. Like in my DMA/non-DMA tests. Both seemed to work just fine though.

Carl Farrington
Associate III

I think it's coming back to me now. I remember reading about clock stretching on i2c.

Is this just a clock stretch, and perfectly fine? It looks like a clock stretch, I just need to see if that's an acceptable stretch for the ssd2828 slave.

T J
Lead

not trying to stretch ? not sure what you mean there...

but yes, it would be continuous clocking because the first transfer does not wait for the byte to progress, only transfer_receive actually waits for the bytes. You may see a slight delay 1-2uSec between received bytes.