STM32 SPI slave mode doesn't transmit properly - Page 1
Forum section: electronics / general

author: nishantnidaria
author_rank: Contributor
timestamp: November 12, 2021, 10:44:35 am
content: I am using an STM32G030C8T6 as an SPI (Slave device) which receives characters from a Raspberry pi (Master device), receiving works perfectly! no problems there, however when i try to send data from STM32 to Pi, it seems that the STM32 gets stuck for a while and Pi rx buffer is filled with only one bit in repeat e.g., if i send char buf = {0,1,2,3,4,5}; Pi receives (111111) or (333333) depending on how many characters i am sending.What do i actually want to do?I want to transmit ADC data from STM32(slave mode) to Pi(master mode), so far it only receives one bit in repeat.Can someone please help me achieve this?Here's my SPI config:Code:void MX_SPI1_Init(void){/* USER CODE BEGIN SPI1_Init 0 *//* USER CODE END SPI1_Init 0 *//* USER CODE BEGIN SPI1_Init 1 *//* USER CODE END SPI1_Init 1 *//* SPI1 parameter configuration*/hspi1.Instance = SPI1;hspi1.Init.Mode = SPI_MODE_SLAVE;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 7;hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE;hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLE;if (HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}/* USER CODE BEGIN SPI1_Init 2 *//* USER CODE END SPI1_Init 2 */}then the functions to read characters, send data and stringCode:char SPI_read(void){// SPI1->SR is the STATUS REGISTER on SPI1 Bus// SPI1->DR is the DATA REGISTER on SPI1 Buschar data;while(!(SPI1->SR & SPI_SR_RXNE));while(SPI1->SR & SPI_SR_BSY);data = SPI1->DR;printmsg("%c",data);return data;}void spi_Send(char caracSend){while(!(SPI1->SR & SPI_SR_TXE));SPI1->DR = caracSend;}void spi_send_string(char* stringSend){int i=0;unsigned int sizeChar = 0;sizeChar = strlen(stringSend);__NOP();for(i=0;i<sizeChar;i++){spi_Send(stringSend);}}Here's my function to receive data from Pi which i call in the main while loop.Code:void SPI_Receive_Commands(void){while(HAL_GPIO_ReadPin(SPI_SS_GPIO_Port, SPI_SS_Pin) == GPIO_PIN_RESET);{HAL_SPI_Receive(&hspi1, (uint8_t *)spi_buf, 10, 100);if(spi_buf == 'v'){memset(spi_buf,'*',10);printmsg("Character V received\r\n");Battery_Voltage();spi_send_string(batteryVoltage);spi_Send('v');printmsg("Sending battery voltage\r\n");}}}Thank you so much for helping me out in advance.

author: AndyC_772
author_rank: Super Contributor
timestamp: November 12, 2021, 11:05:26 am
content: Without looking through your code in detail, my first guess would be timing. You're getting repeated characters because nothing is writing to SPI->DR quickly enough, and so the SPI peripheral underruns and just repeats the last thing that was written.It looks as though you're waiting to receive a 'v' from the master via SPI, then going off and making a measurement, then writing the results of that measurement to the SPI interface a byte at a time in software.Meanwhile, the master is clocking SCK as fast as it wants to. (How fast is this?)This is a common problem with any slave device; it must be able to provide a valid response at the precise moment a clock edge arrives from the master. In the case of an SPI slave, this means you have the time between the last bit of the 'v' being received and the very next transition on SCK to decide what you're going to send, and write the first byte into the SPI Tx shift register.That's a tall order.You certainly don't have any time to actually go and make a measurement; that needs to have already been done, and the result stored somewhere in memory so it can be fetched at essentially zero notice.For reliability, your slave device really needs to have a valid response already stored in RAM, and a DMA channel set up to write it a byte at a time into the SPI Tx register as soon as SCK starts toggling.So, you might change your protocol. Instead of receving a 'v' then deciding what to do about it, you instead make a voltage measurement whenever it's convenient, then set up a buffer containing (say) "V3.72v<EOL>" and have a DMA channel configured to write it to the SPI port. When the master is ready to read data from your device, it just transmits zeros on MOSI (which you can safely ignore), and the DMA channel writes a valid response which appears on MISO - regardless of what your CPU is doing at the time.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 12, 2021, 11:09:55 am
content: The problem seems to be that you don't understand how SPI works. Look it up on Wikipedia for example.The main idea is, a slave cannot actively send anything. Only master sends, and when master sends, slave needs to have data ready already. Data is just exchanged.On register level this means, you need to write to the data register BEFORE master initiates the transfer. This also means the data will be old.There are really many different ways to implement getting "recent" data, one quite typical would be detecting the nCS edge and very quickly write the data to the DRbeforemaster starts clocking SCK. Or, you can make the master do a long enough transaction, and at slave, listen for the first byte. Your reply will be whatever nonsense (or some magic number) during this initial byte because you can't know in advance what master is asking you to do. But you can start writing actual content after 1-2 bytes (depending on how you handle timing).If you want to use the pattern that you use in your code, then SPI is just wrong tool for the job; UART works that way (as two unidirectional, asynchronous streams).But SPI is great for sharing data. Like "show me yours, I'll show you mine."

author: nishantnidaria
author_rank: Contributor
timestamp: November 12, 2021, 02:24:30 pm
content: I tried the following with HAL_SPI_Transmit_DMA but now it doesn't send anything at all and MCU doesn't get stuck:Code:void SPI_Receive_Commands(void){while(HAL_GPIO_ReadPin(SPI_SS_GPIO_Port, SPI_SS_Pin) == GPIO_PIN_RESET);{HAL_SPI_Receive(&hspi1, (uint8_t *)spi_buf,10,500);if(spi_buf == 'v'){memset(spi_buf,'*',10);printmsg("Character V received\r\n");char dataBuf = {'a','b','c','d','e','v'};HAL_SPI_Transmit_DMA(&hspi1,(uint8_t*)dataBuf,6);//spi_send_string(batteryVoltage);//spi_Send('v');printmsg("Sending battery voltage\r\n");}}}

author: AndyC_772
author_rank: Super Contributor
timestamp: November 12, 2021, 02:50:58 pm
content: That's because you need to set up the DMA transferbeforeany clocks arrive on SCK.Youcan'thave a work flow which is:- receive a character via SPI- spend time thinking about what to send in response- prepare data and set up DMA...because by the time your DMA is set up, the master has long since finished generating clocks on SCK.Think: what's happening on SCK and MISO while your code is executingprintmsg()?Instead:- make voltage measurement- convert it into a string of digits in your preferred data format- set up a DMA channel to write this data into the SPI Tx register- sit back and wait; the master will generate SCK whenever it likes, and this is what will cause the DMA controller to actually issue writes to the SPI port.

author: nishantnidaria
author_rank: Contributor
timestamp: November 12, 2021, 04:13:11 pm
content: Thanks I really appreiciate your replies!That's what i am doing now:ADC measurement in the main function just once then store the data in a buffer which then i transmit using DMA.Do i need do the spi_send within the DMA function? or create a special function ?Sorry i am a bit new to STM32, i could really use some examples if you have any? As it would help me understand it better.

author: AndyC_772
author_rank: Super Contributor
timestamp: November 13, 2021, 08:25:38 am
content: Your spi_send function is a loop which writes characters one at a time to the SPI port in software. This might work with a very slow SCK, but there's still the fundamental problem that you don't want your CPU to be sitting in that tight loop all the time waiting for SCK.In most devices, what happens when you write a byte to the SPI interface is:- if the Tx shift register is empty, the byte is immediately transferred to it. If not, the byte is held in a temporary (holding) register until it is.- each edge on SCK causes the shift register to advance one bit, and the bit that's shifted out appears on MISO- as soon as the last bit has been shifted out, the contents of the holding register are transferred to the shift register- at this point, the holding register is empty, and you must ensure it is written with the next byte before the shift register also becomes empty.This all means that you must ensure the holding register is updated each and every time it becomes empty, otherwise the SPI interface will underrun (ie. there's nothing new to copy into the shift register at the moment it empties) and you'll get duplicated characters transmitted.The problem with this is you have absolutely no ideawhenthe holding register becomes empty, unless you're sitting in a tight loop polling it. The CPU literally cannot do anything else - not even service an interrupt - in case it misses that critical moment when the 8th SCK has been received and there's just a few instructions' worth of time to write to the holding register again.This is where DMA comes in. Once a DMA channel is set up, the SPI interface itself can indicate to the DMA controller each time the holding register has become empty, and this causes a new byte to be transferred without the CPU itself being involved at all.So you don't need spi_send. Just delete it.To reiterate:- set up SPI port in slave mode- make voltage measurement, translate into whatever string of digits you'll transmit to SPI- set up a DMA channel to write those digits to the SPI Tx register, and enable it.- go off and do whatever other things the CPU needs to do.What you'll see, if you check the DMA controller's registers, is that exactly one byte gets transferred immediately; this is the write of the first byte into the holding register. There's a hardware link between the SPI interface and the DMA controller which tells it not to write any further bytes until the holding register becomes empty. Once SCK starts toggling, the DMA controller will keep transferring a byte at a time to the SPI port.Don't forget that you'll need some way for the STM32 to indicate to the RPi that there is actually valid data ready to transfer. In a system that needs to be reliable, the RPi needs to know when the SPI interface is ready to transfer data, and when it isn't. You don't want an SPI read to occur while you're busy updating the measurement or reconfiguring the DMA controller. Typically this is done with a separate GPIO, configured to be a 'data ready' output. This is polled by the master, or used to generate an interrupt.

author: Siwastaja
author_rank: Super Contributor
timestamp: November 13, 2021, 09:35:24 am
content: Also ASCII over SPI doesn't make any sense, IMHO. You just add the complications of generating and parsing the strings.I understand it when using UART because that's inherently a "stream" like interface and there's a real advantage being able to just tap any terminal program into the communication and look it by eye.Really, if OP wants to keep doing things that way, just use UART. SPI requires a complete "paradigm shift" in thinking.

author: nishantnidaria
author_rank: Contributor
timestamp: November 18, 2021, 04:39:57 pm
content: Thank you so much for the explaination it helped and fixed my problem!for me it was the gpio pin which i wasn't using to indicate the master device.