STM32G0 as SPI slave - detecting transaction beginning & ending - Page 1
Forum section: electronics / general

author: yaqwsx
author_rank: Contributor
timestamp: May 02, 2019, 07:21:06 pm
content: I am using STM32G071 in my latest project. The MCU is supposed to act as an SPI slave in half-duplex mode. The protocol is rather simple - master pulls CS low, clocks out a command, slave responds and master ends the transaction by pulling CS high. I have successfully got the SPI peripheral working using the SPI LL API. However, I am struggling with detecting the beginning and the end of a transaction. I was expecting the SPI peripheral generates an interrupt on the beginning/ending of the transaction, however, it is not the case. How can I get a notification about transaction beginning and end? I am reading through the reference manual and I see no solution.

author: ShowKemp
author_rank: Contributor
timestamp: May 02, 2019, 08:55:19 pm
content: You can use the slave Chip Select line to generate interrupts in falling (beginning) and rising (ending) edge.

author: yaqwsx
author_rank: Contributor
timestamp: May 02, 2019, 08:59:50 pm
content: However, the pin can work only as an AF for SPI or interrupt If I understand Reference manual correctly. I could turn CS into software mode, however, then I am struggling how to enable it. I looked that the older API had function SPI_NSSInternalSoftwareConfig, but LL seem to miss an alternative.

author: newbrain
author_rank: Super Contributor
timestamp: May 03, 2019, 08:28:58 am
content: I don't have the G0 Cube downloaded, but the SPI peripheral seems to be the same as the one used in F0s, so I expect the LL library to be very similar.If you are using the full LL library (USE_FULL_LL_DRIVERdefined), the mode for NSS is determined by theNSSfield ofLL_SPI_InitTypeDefstruct passes toLL_SPI_Init().IfUSE_FULL_LL_DRIVERis not defined, the mode can be set byLL_SPI_SetNSSMode().In both cases, the NSS field/parameter values is one of:LL_SPI_NSS_SOFTLL_SPI_NSS_HARD_INPUTLL_SPI_NSS_HARD_OUTPUTOtherwise, a direct manipulation of the SSM (set to 1 for SW management) and SSI bits in SPI_CR1 register is also possible.

author: mikerj
author_rank: Super Contributor
timestamp: May 03, 2019, 08:46:54 am
content: Maybe describe what you are trying to do? Typically you only need to know if there is data in the RX buffer when receiving, or the TX buffer is empty and requires more data when transmitting.

author: yaqwsx
author_rank: Contributor
timestamp: May 03, 2019, 10:47:35 am
content: Ok, a simple example I am trying to implement to find out what am I doing wrong: Each transaction starts by master pulling CS low and continues by master sending 10 bytes and slave responding with 10 bytes to master. The transaction ends with master pulling CS high. If the transaction is incomplete (e.g. not all bytes were sent), it should not affect the other transactions (this is why I have to detect the CS line - to reset incomplete transactions).I have written following a simple demo to fulfill the task above using the advice to write SPI_CR1_SSI directly:Code:int rxTriggCount = 0;int txTriggCount = 0;int rxCounter = 0;bool print = false;char rxBuffer;const char *txBuffer = "Hello world!";int txCounter = 0;extern "C" void SPI1_IRQHandler() {if (LL_SPI_IsEnabledIT_RXNE( SPI1 ) && LL_SPI_IsActiveFlag_RXNE( SPI1 ) ){rxBuffer[ rxCounter++ ] = LL_SPI_ReceiveData8( SPI1 );if ( rxCounter == 10 ) {LL_SPI_SetTransferDirection( SPI1, LL_SPI_HALF_DUPLEX_TX );LL_SPI_DisableIT_RXNE( SPI1 );LL_SPI_EnableIT_TXE( SPI1 );}rxTriggCount++;}if ( LL_SPI_IsEnabledIT_TXE( SPI1 ) && LL_SPI_IsActiveFlag_TXE( SPI1 ) ){LL_SPI_TransmitData8( SPI1, txBuffer[ txCounter++ ] );if ( txCounter == 10 )LL_SPI_DisableIT_TXE( SPI1 );txTriggCount++;}}int main() {HAL_Init();LL_APB2_GRP1_EnableClock( LL_APB2_GRP1_PERIPH_SPI1 );NVIC_SetPriority( SPI1_IRQn, 2 );NVIC_EnableIRQ( SPI1_IRQn );LL_GPIO_InitTypeDef GPIO_InitStruct{};GPIO_InitStruct.Pin = LL_GPIO_PIN_1;GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;GPIO_InitStruct.Alternate = LL_GPIO_AF_0;LL_GPIO_Init( GPIOA, &GPIO_InitStruct );GPIO_InitStruct.Pin = LL_GPIO_PIN_6;GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;GPIO_InitStruct.Alternate = LL_GPIO_AF_0;LL_GPIO_Init( GPIOA, &GPIO_InitStruct );LL_SPI_InitTypeDef SPI_InitStruct{};SPI_InitStruct.TransferDirection = LL_SPI_HALF_DUPLEX_RX;SPI_InitStruct.Mode = LL_SPI_MODE_SLAVE;SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_LOW;SPI_InitStruct.ClockPhase = LL_SPI_PHASE_1EDGE;SPI_InitStruct.NSS = LL_SPI_NSS_HARD_INPUT;SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;SPI_InitStruct.CRCPoly = 7;LL_SPI_Init( SPI1, &SPI_InitStruct );LL_SPI_SetRxFIFOThreshold( SPI1, LL_SPI_RX_FIFO_TH_QUARTER );LL_SPI_Enable( SPI1 );LL_SPI_EnableIT_RXNE( SPI1 );GpioA[ 4 ].setupInterrupt( LL_EXTI_TRIGGER_RISING_FALLING, []( bool rising ) {if ( rising ) {// Transaction endsSPI1->CR1 &= ~SPI_CR1_SSI;rxBuffer[ rxCounter + 1 ] = 0; // Terminal zeroprint = true;rxCounter = 0;txCounter = 0;LL_SPI_DisableIT_TXE( SPI1 );LL_SPI_EnableIT_RXNE( SPI1 );LL_SPI_SetTransferDirection( SPI1, LL_SPI_HALF_DUPLEX_RX );// Preload dataLL_SPI_TransmitData8( SPI1, txBuffer[ txCounter++ ] );}else {// Transaction beginsSPI1->CR1 |= SPI_CR1_SSI;}} );while ( true ) {if ( print ) {counter++;print = false;Dbg::info("Received %d (%d, %d): %s", counter, rxTriggCount, txTriggCount, rxBuffer );rxTriggCount = txTriggCount = 0;}}}The debug print shows expected values - I correctly receive 10 bytes from master and 1 + 9 bytes are pushed into the TX FIFO. However, the master receives no data. Am I missing something with chaning the direction of SPI?Also: I grepped the sources of the LL and HAL library and there is no function which would change the direction of the SPI using SPI_CR1_SSI.

author: newbrain
author_rank: Super Contributor
timestamp: May 03, 2019, 05:16:16 pm
content: Quote from: yaqwsx on May 03, 2019, 10:47:35 amI was probably not 100% clear, I had interpreted this:Quote from: yaqwsx on May 02, 2019, 08:59:50 pmas a question on how to set SW mode for the NSS, and was only answering to that.The SSI bit won't change direction - it just acts as a SW version of the NSS pin.The bits that control, respectively, full/half duplex and the Tx/Rx in HD are BIDIMODE and BIDIOE, still in CR1.With the LL, the function that manipulates those bits isLL_SPI_SetTransferDirection(SPI_TypeDef *SPIx, uint32_t TransferDirection)that will takeLL_SPI_HALF_DUPLEX_RXLL_SPI_HALF_DUPLEX_TXfor its TransferDirection parameter.Finally:Wow! Lambda expression!I think this is the first time I see it in this forum section (I, myself, am more of a C person).Are you using some known library or is it your doing?

author: yaqwsx
author_rank: Contributor
timestamp: May 04, 2019, 07:24:58 am
content: newbrain: My bad. I have written it wrong in the post; the SSI bit should start and stop transmission (i.e. reset shift registers of the SPI).Therefore; in the interrupt on CS pin, I reset/enable the frame using SSI bit, set the right direction using LL_SPI_SetTransferDirection and reset my buffers. Then, in the SPI interrupt, I change direction after reception of 10 bytes, again using LL_SPI_SetTransferDirection. Then I put 10 bytes in TX FIFO using TX SPI interrupt. However, my master reads no data. It seems like the MISO/MOSI output pin did not change the direction. Is there something I forgot to setup?About the last note: Thanks. I am a C++ developer and I program MCU in C++ for several years now. However, this time it is the first time for STM32. I have written only a thin wrapper for the LL library which allows me to set up the peripherals in a simple way, leverage C++ RAII for resource handling and to easily setup functions, methods, and lambdas as interrupt handlers. If you are interested, the code will be publicly available once I finish the first version of firmware, so I can send you a link.

author: donotdespisethesnake
author_rank: Super Contributor
timestamp: May 04, 2019, 08:04:57 am
content: I don't think your method of using SPI is correct, or likely to work. You seem to be treating it like a UART. The problem with SPI is that the master dictates the transfer because it drives the clock, so the slave must always be ready to shift in data. Reconfiguring the SPI on the fly will probably take to long.I think you need to have everything set up before you start a transaction, and only transfer in one direction per transaction. Really for bidirectional transfer you need MOSI and MISO, are you that short of pins?It seems like you should be using a UART for this anyway.

author: yaqwsx
author_rank: Contributor
timestamp: May 04, 2019, 08:23:51 am
content: The protocol is given externally. The master sends a command, then stops the clock for a given period of time (while preserving CS low) and then starts the clock again and reads the data. There is already an implementation of such a slave (however it does not use STM32 but AVR).

author: newbrain
author_rank: Super Contributor
timestamp: May 05, 2019, 07:26:42 pm
content: Now I see what you are trying to do.I think I'll agree with donotdespisethesnake, it might take too long to reconfigure the SPI when seeing the falling/rising edge of NSS and propagate the state to SSI.I experimented a bit with a couple of F042: a possible way to do what you need (mostly, taking care of aborted transactions) could be to us the HW NSS mode on the half duplex slave, and tie an EXTI enabled pin in parallel to catch an early NSS rise.The receive - transmit switch mid transaction works correctly if given enough time.Consider also the possible use of DMA, to lighten up the CPU use.OT: By playing around with a half-duplex master, I discovered that changing the BIDIOE bit from 1 (Tx) to 0 (Rx) on the fly does not work: I need to disable and re-enable the SPI to see the clock going. Not that useful in any case, as stopping reception on HD master is an exercise in tight timing...In the end, for the master, I resorted to a full duplex setup: MISO and MOSI tied together, and playing with the pin mode to disable the MOSI output driver when receiving.

author: yaqwsx
author_rank: Contributor
timestamp: May 07, 2019, 01:09:25 pm
content: Finally, I got the basic communication working. It is possible to use an alternate function and EXTI for a pin at the same time - they do not interfere. Also, it is possible to switch direction in the middle of a transition.However, as a next step, I tried to use DMA for reading and writing. Currently, I face a weird bug - when reading, first four bytes from SPI are read correctly, all the other bytes are read as the first one. E.g., instead of "ABCDEFG" I receive "ABCDAAA". I think it has to do something with the SPI FIFO. However, I think I have set it up correctly - I use 8-bit byte and the threshold is one quarter. What could be wrong in my configuration of SPI and DMA? The DMA code is basically as follows:https://pastebin.com/Ah62dE6m