ST 32F4xx SPI - weird behaviour with multiple slaves and clock polarity & phase - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2021, 05:16:28 pm
content: I have ~5 devices on one SPI controller.The clock waveform is well controlled ringing-wise.I am finding some weird things, when accessing one device after another. Obviously the controller needs to be reconfigured, for different clock rates, and these two parameterswhich determine whether the clock is parked low or high, and whether the data is clocked on the rising or the falling edge.Obviously there must be clock transients when the controller is re-initialised but this should not matter because none of the slaves have their /CS asserted then!What I am finding is that with some slaves needing (per data sheet) CK parked low and some parked high, they don't play together well. But if I config everything to parked-low, it works. I've done enough hardware design (including FPGA/ASIC) to know that only theedgeshould be used to clock stuff, so I would expect the CK parking level to be immaterial, but there may be stupid designs out there which are sensitive to CK state when CS is asserted (or de-asserted).For example the STLED316 wants it parked highwhile this chip wants it lowbut in reality neither cares, so long as both are the same, and it works best if both/all are low. The STLED316 is quite easily messed up, I find. But as far as the really simple setup of the two parameters go, it seems to work with both if used in isolation.There isn't going to be anything one can do about glitches. I am leaving the CK pin as pushpull output the whole time, so at some point during the re-init it must swap from L to H to L etc.The manifestation of the issues can be a non-working slave but more often it looks like the slave is getting an extra clock, or missing a clock.What do other people do to handle this sort of thing?This is quite a good site, with a tablehttps://visualgdb.com/tutorials/arm/stm32/spi/

author: harerod
author_rank: Frequent Contributor
timestamp: February 23, 2021, 06:42:55 pm
content: Hi peter-h,quick question: are you using CubeMX/HAL or do you program bare metal?When analyzing a STM32F407 / CubeMX / HAL / SPI-master problem with an oscilloscope, I found that, depending on SPI phase/polarity, the initial clock/data levels after setup might be wrong. Since that bus has a whole bunch of SPI-slaves, I have to control the NCS-pin by firmware anyways. So simple solution: after changing config, keep all NCS idle/high and send a single packet.After that the idle polarity of the clock/data lines correspond to the configuration. That simple fact brought additional fun to debugging this, err, unexpected feature.Just clean your pipes after init, so to say.I haven't had the time to check whether this behaviour is due to HAL or hardware. When programming bare metal and having enough time reserves, I prefer bit-bongo (my more musical version of bit-banging) for synchronous interfaces like SPI or I2C.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2021, 06:59:51 pm
content: STM32 Cube IDE. I don't understand more than 1% of this environment; I sort of inherited it from a much smarter programmer. I can use the breakpoints thoughIt's not that reliable; every so often one has to reboot the PC (win7-64) to avoid that ST-LINK_GDB_server port xxxxx error.That's an excellent point you make. I did wonder, from the hardware designer's POV, how the hell ST switch the clock modes etc around when the master is not clocking anything out; basically not running at all. The first byte probably will come out with some garbage clock config. It is not as if they are running software (microcode) to set this up.That would also explain why having the same clock-parking config on all devices makes it work, of course.I would prefer to not have to send out a dummy byte after each controller reconfig, due to the time it may waste in some cases.

author: harerod
author_rank: Frequent Contributor
timestamp: February 23, 2021, 07:38:07 pm
content: I am grateful to STM32, for making those chips so damn interesting. I earn handsome money by teaching people, who are smarter than me, STM32 basics. Took me several years to get into a position to do this. At least the documentation has improved over the last 15 years. I prefer bare metal programming, but knowing that machine well enough I can fix / work around HAL issues within reasonable time. Anybody new, being lured to this device by HAL is in for a tough time.If your time is of any monetary value, I would strongly recommend getting a different JTAG-debugger. Right now, next to me stands a machine with three interacting STM32s. My primary debugging target is usually connected via a small black SEGGER J-LINK mini, the others via white ST-Links. There is something to the saying "once you go black, ..."Disclaimer: although I met and chatted on occasion with their competent and kind head of development, I have no other ties with SEGGER, except sending business (e.g. all my customers' developers) their way.

author: harerod
author_rank: Frequent Contributor
timestamp: February 23, 2021, 07:46:24 pm
content: Quote from: peter-h onTodayat 07:59:51 pm

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2021, 08:57:45 pm
content: I did look up those Segger boxes. 400 and 800 quid - impressive! Would they work with CubeIDE, and what do they do better? The one I am using is the ST-LINK ISOL model.My background goes back to 1970s and the Z80 etc, hw and sw. And I have always used standalone assembler or compiler, with a makefile or, more recently and with super-fast computers, just a batch file. But this ST environment is way way more complicated... OTOH it supports debugging.The HAL stuff is a mystery to me. My colleague understands it, but to me it just looks like a load of code which ST wrote where they #define thousands of pin names etc. The CPU doesn't actually have a hardware abstraction layer. The C code goes direct to the hardware. The Cube IDE thing does code generation so you can define say a DMA linked DAC and it generates code for you, but the code is surrounded by a load of garbage. You have to extract just the bit you want and throw the rest away. And the drivers they give you for e.g. USB and ethernet are full of bugs. But this is digressing. I just find the HAL_ stuff impenetrable.

author: harerod
author_rank: Frequent Contributor
timestamp: February 23, 2021, 09:23:06 pm
content: Mmmh, I usually keep a few fresh ST-Links around, as a throwaway to give to customers for tests and such. I also have an isolated ST-Link, in case I don't trust the hardware. The J-links become more interesting with growing project complexity. You can handle any STM32 with an ST-Link, the J-Link will simply make the experience smoother. Be sure to get a version with unlimited breakpoints. The higher clock rate keeps things rolling when you handle more data of any kind. The debug stability is better and the failure handling more graceful. Do I sound like a used car salesman already? :)And all this without the additional software SEGGER has in their program (which I rarely use). I worked with IAR, and later the GCC IDEs ( makefile/GCC, CooCox, Atollic, now Cube ). I didn't touch CubeIDE till last year.On the other hand, the ST-Link is a great throwaway for small-scale production, since it can be easily controlled by shellscripts and doesn't cost much.I too remember the 1970s. Right after I quit soiling my pants on a daily basis, I started taking electrical stuff apart. In the mid 1980s I finally started actually fixing broken stuff.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2021, 10:19:16 pm
content: Looking at HAL_SPI_Init the ST generated code seems to disable the SPI:

author: tszaboo
author_rank: Super Contributor
timestamp: February 23, 2021, 10:27:09 pm
content: In my experience, it is impossible to figure out clock polarity and phase from datasheets.Game theory suggests that you have 25% chance to get it right. It's actually 0%.The only way to do it, is to test all 4 configurations, and pick the one that works.

author: peter-h
author_rank: Super Contributor
timestamp: February 23, 2021, 11:01:37 pm
content: What if 2 or 3 work?Some may be marginal, or temp dependent...Also if the wrong clock edge works, that must be highly suspicious. No digital logic could possibly sample data on both edges.Update: I have now added a transmit of 1 byte to each SPI re-init. Time will tell if it helps, in terms of artefacts on my LED display or duff data coming out of some chips I am using.But on one chip, an MCP3550 from which you always read 3 bytes (and never write anything, but of course SPI always writes a byte for each one it reads) gets screwed up by this single byte; in fact the whole SPI channel gets screwed up. I reckon setting up SPI for a 3 byte transfer and then sending 1 byte screws up the controller for ever, because it needs the other two. However if I do 3 bytes, that screws up the MCP3550. But... this is the only device which behaves like that. There is no such issue with an ADS1118 which uses 2-byte transfers.Edit: just realised there is no 3 byte SPI mode. There is only 8 bit or 16 bit. So if you initialise a devicelike an MCP3550 which transfers 3 bytes, it is ok to transfer justonedummy byte first.

author: harerod
author_rank: Frequent Contributor
timestamp: February 24, 2021, 06:01:26 pm
content: Quote from: NANDBlog onYesterdayat 11:27:09 pm

author: peter-h
author_rank: Super Contributor
timestamp: February 24, 2021, 07:29:46 pm
content: I now have everything working, and using the SPI configs per device data sheets.It seems to have been a combination of very subtle things. I put in delays of 1-2us here and there, so you don't have back to back transfers to different-config devices too close together. No obvious reason for this other than some undocumented requirement for x clocks for the SPI controller to set up a new config, especially a new clock divider. One device is running with /8 and another with /64.I am told this sort of thing is normal with SPI...There is no reliable way to do microsecond-level delays on the 32F but what works is a sequence of identical pin setting instructions e.g.One could use a hardware timer and poll it, and I used used that in years past to do RS485 driver enable timing (where a UART didn't have an "all sent" status bit, and didn't have an output pin which is active all the time data is coming out) but this is messy.

author: harerod
author_rank: Frequent Contributor
timestamp: February 24, 2021, 08:21:20 pm
content: A microsecond delay would be a perfect excuse to play with ARM assembly. Here is some stuff that I have been using for a while. This code should run on M0, M3 and M4 cores.That snippet resides in a file called hrtdelayX.s, the assembly-function behaves like a normal c-function.Code:// C-declaration: extern void hfwdelay32(uint32_t delay); // declared in hfwtimer.h// C-call: hfwdelay32( uint32_t );// assembly delay in external file, to avoid trouble with optimization/loop unroll// MAH1406171314.section .text.hrtDelay32,"ax" // text section, function name, "ax" executable.weak hrtDelay32.type hrtDelay32, %function.syntax unified // tell assembler to use extended thumb2 instruction sethrtDelay32: // the actual function pointerhrtDelay32_loop:cmp r0, #0 // compare start to endit gt // begin if-then block -> greater than (up to 4 instructions may follow)subgt r0, #1 // downcountbgt hrtDelay32_loop // branch if greaterbx lr // return via link register.end hrtDelay32.size hrtDelay32, .-hrtDelay32

author: julian1
author_rank: Frequent Contributor
timestamp: February 24, 2021, 08:29:47 pm
content: I've thought about this - on the fly reconfiguration of the spi port. But always been too fearful to try, and end up selecting a mcu with enough ports for each spi device type. Good to know that it can be made to work, and that clock polarity and resting get correctly orientated, with some extra programmed deadtime.

author: tszaboo
author_rank: Super Contributor
timestamp: February 24, 2021, 08:56:19 pm
content: Quote from: harerod on February 24, 2021, 06:01:26 pmI dont think the human brain is well wired to handle this. And some manufacturers didn't adapt the naming from Motorola. There was even a convention of SPI mode 0,1,2,3. How hard would it b, to write this single line in the datasheet of all the devices. This flash memory uses mode 0. Ti does this, sometimes.https://www.ti.com/lit/ds/symlink/sm28vlt32-ht.pdfMode 0 and 3. See that wasn't that hard.Now what happens if you connect more than 1 peripheral on the same SPI line, and one works in Mode 1 and the other in mode 2? What if I connect a 74HC795 on an SPI interface.What if I dont write code, and leave all this bullshit to the FW developers? That's what I did, look at me I turned out to be fine!

author: peter-h
author_rank: Super Contributor
timestamp: February 24, 2021, 09:10:07 pm
content: Assembler (I've done megabytes of it, way back) is still susceptible to cache timing variations. And interrupts, although those should only ever lengthen the time (but there are some 'max' times, too).Successive pin set instructions should not get optimised out, AFAIK, because the compiler cannot possibly know you are not doing this for some hardware reason.Is that the difference between 0 and 3? What I don't get is why the parked CK level should be relevant. Surely the /CS should merely enable the clocking of the data, which happens on +ve edges anyway.I may have seen, though I can't be sure, a case where having the wrong parked CK level when CS goes to 0, might generate an extra clock, but that is stupid logic design. Itmayhave been on the STLED316 (which I am writing-only, but can see glitches on the LED display rather easily) or the MCP3550 (which is read-only).My conclusion so far is just that the 32F417 needs mysterious delays when switching SPI configs, of the order of 5-10us.Does anyone have a code snippet for loading a hardware timer and looping on it until zero (or overflow)? It should be trivial.

author: mikerj
author_rank: Super Contributor
timestamp: February 24, 2021, 09:12:56 pm
content: Quote from: NANDBlog on February 24, 2021, 08:56:19 pmThank goodness it's not just me. As you say a very few datasheets do specify a mode, the majority give an ambiguously drawn clock diagram and leave it at that. Worst thing is it's quite possible to get some peripherals working in the wrong mode, but with such marginal timing they'll break on the next full moon.

author: mikerj
author_rank: Super Contributor
timestamp: February 24, 2021, 09:19:57 pm
content: Quote from: peter-h on February 24, 2021, 09:10:07 pmFWIW the two config bits control polarity (rising or falling edge) and phase (leading or trailing edge), the "parked" level is simply a consequence of combining those two things and not the actual property that's being configured. However if the "parked" level isn't correct for the configured mode it will be impossible to correctly clock the first bit (assuming it corrects itself after that).

author: peter-h
author_rank: Super Contributor
timestamp: February 24, 2021, 09:27:54 pm
content: "However if the "parked" level isn't correct for the configured mode it will be impossible to correctly clock the first bit "Unless the clock changes state first, so that a +ve edge can be achievedLet's say you have a chip which - like most - clocks the incoming data on the +ve edge.It should not matter where the clock is initially parked. But from the POV of the Controller it may affect how the first bit comes out...The confusing thing is that the data sheets imply a certain parked clock levelwhen CS goes low, and when it goes high later, and I think this is garbage.BTW I don't think that shifting a byte out after each reconfig actually worked or did anything useful. It totally broke a couple of the devices. So I think the controller doesn't want you to be loading data in there, until it has completed its reconfiguration. Better to just wait.

author: harerod
author_rank: Frequent Contributor
timestamp: February 25, 2021, 01:45:37 pm
content: Quote from: peter-h onYesterdayat 10:27:54 pm

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2021, 03:35:48 pm
content: I have no evidence to suggest the extra byte sent out broke any of theslaves.The behaviour looked like it broke thecontroller. The code ran very slowly, suggesting that some timeout(s) (which ST code is full off - even in places where only defective silicon will trigger the condition) were running to completion, waiting for something.Broken slaves would not have slowed down my code. It would just have produced garbage data in places.To get more info a lot more time would be spent with debugging, and a scope, but in the absence of hardware breakpoints and a real time trace (which basically ended in the 1990s, because an ICE could not be built which was fast enough) it would be hard work.

author: Siwastaja
author_rank: Super Contributor
timestamp: February 25, 2021, 04:36:36 pm
content: Do not try to use the HAL library functions.The underlying ST's hardware peripheral isalreadydifficult enough. The HAL limits the feature set of the peripheral,andadds new error sources that shouldn't be there in the first place. I despise of embedded libraries that add error return codes (necessitating error checking and handling) for thingsthat cannot fail by design. Similarly, infinite loops that poll for some flag are evil, but sometimes necessary. Infinite loop should only be there explicitly by the application programmer. Adding a timeout is a crappy fix, it's still nearly-infinite loop in the timescale of embedded applications. These shouldn't be in a library.So now we have a library that requires 100 lines of code to operate, contain made-up error sources, made-up timeouts, require error handling, and still can't do the job, while the bare metal solution would have been 20 lines of code where every interaction is visible to the programmer.If you want to get SPI running smoothly and reliably on an ST microcontroller, it's going to take some time and digging into it no matter what. Do it bare metal and you are one step closer to solving all those problems the library promised to solve but didn't.

author: harerod
author_rank: Frequent Contributor
timestamp: February 25, 2021, 07:54:38 pm
content: Quote from: peter-h onTodayat 04:35:48 pmecontroller. The code ran very slowly, suggesting that some timeout(s) (which ST code is full off - even in places where only defective silicon will trigger the condition) were running to completion, waiting for something....Help me out here, please - "broke", as in physically damaging the STM32 microcontroller?

author: ajb
author_rank: Super Contributor
timestamp: February 25, 2021, 08:00:28 pm
content: Agree with Siwastaja, in general I have not found HAL to be all that much of a time saver, and I ESPECIALLY would not trust it to handle unusual use cases like on-the-fly reconfiguration.Regarding ST-Link versus J-Link conversation from upthread: J-Links are noticeably faster at flashing devices, and can easily save tens of seconds per cycle on devices with larger memories. This eventually adds up to a fair bit of time savings, especially when fiddling around in the hardware peripherals where you tend to do a lot of programming cycles. Personally I find the time savings are enhanced by not giving my brain enough time to wander off into la la land while waiting for a device to program and then needing to remember what I was doing once it's done 🙃. But also the J-Links are just plain more reliable. I've found ST-Links will often get into weird states where they need to be unplugged and replugged from USB, and never had that problem with a J-Link.

author: peter-h
author_rank: Super Contributor
timestamp: February 25, 2021, 09:11:29 pm
content: Siwastaja's comments resonate with my view of it totally.However, if one is new to the device, one can get moving fairly quickly by using the ST code generator and this saves studying their 2000 page reference manual, plus the 1000 pages of other stuff. I was doing all the hardware and even though I used the circuits of their dev kit as a starting point, I did actually read every page of the 1000 page hardware manual.Then one can strip the generated code down to the minimum if one really wants to.Yes the ST-link does need unplugging maybe every 50 uploads. Can the J-link be used with Cube IDE?No it didn't damage the silicon, harerod. It just made my code run very slowly - about 10x slower. The only mechanism I can see is that some/all of the SPI calls were running slowly, and looking at the code it had to be timeouts.

author: ajb
author_rank: Super Contributor
timestamp: February 25, 2021, 10:03:00 pm
content: Quote from: peter-h on February 25, 2021, 09:11:29 pmYep, I believe Cube IDE is evolved from the Eclipse-based Atollic TrueStudio that ST bought a couple of years ago, which actually used to support several different manufacturers' parts via J-Link. It's just gdb under the hood AFAIK, so it works on a wide range of platforms. I'm currently using a J-Link on an ATSAMD project in Microchip Studio, and if you want something lighter-weight, Segger has their own standalone debug software called Ozone that works pretty well. It operates from the elf file generated by your toolchain of choice so you can easily rebuild while debugging in Ozone and it picks up changes automatically.

author: peter-h
author_rank: Super Contributor
timestamp: February 26, 2021, 09:51:52 am
content: This is the kind of complete bollocks which the ST code generator produces. OK; much of it is #defined out, but still most of the error handling is purely bogus. Writing to SPI TX cannot fail assuming it was initialised right, and if it wasn't you will get useless (in an embedded system) timeouts and callbacks, etc.Code:/*** @brief Transmit and Receive an amount of data in blocking mode.* @param hspi pointer to a SPI_HandleTypeDef structure that contains* the configuration information for SPI module.* @param pTxData pointer to transmission data buffer* @param pRxData pointer to reception data buffer* @param Size amount of data to be sent and received* @param Timeout Timeout duration* @retval HAL status*/HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,uint32_t Timeout){uint16_t initial_TxXferCount;uint32_t tmp_mode;HAL_SPI_StateTypeDef tmp_state;uint32_t tickstart;/* Variable used to alternate Rx and Tx during transfer */uint32_t txallowed = 1U;HAL_StatusTypeDef errorcode = HAL_OK;/* Check Direction parameter */assert_param(IS_SPI_DIRECTION_2LINES(hspi->Init.Direction));/* Process Locked */__HAL_LOCK(hspi);/* Init tickstart for timeout management*/tickstart = HAL_GetTick();/* Init temporary variables */tmp_state = hspi->State;tmp_mode = hspi->Init.Mode;initial_TxXferCount = Size;if (!((tmp_state == HAL_SPI_STATE_READY) || \((tmp_mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES) && (tmp_state == HAL_SPI_STATE_BUSY_RX)))){errorcode = HAL_BUSY;goto error;}if ((pTxData == NULL) || (pRxData == NULL) || (Size == 0U)){errorcode = HAL_ERROR;goto error;}/* Don't overwrite in case of HAL_SPI_STATE_BUSY_RX */if (hspi->State != HAL_SPI_STATE_BUSY_RX){hspi->State = HAL_SPI_STATE_BUSY_TX_RX;}/* Set the transaction information */hspi->ErrorCode = HAL_SPI_ERROR_NONE;hspi->pRxBuffPtr = (uint8_t *)pRxData;hspi->RxXferCount = Size;hspi->RxXferSize = Size;hspi->pTxBuffPtr = (uint8_t *)pTxData;hspi->TxXferCount = Size;hspi->TxXferSize = Size;/*Init field not used in handle to zero */hspi->RxISR = NULL;hspi->TxISR = NULL;#if (USE_SPI_CRC != 0U)/* Reset CRC Calculation */if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE){SPI_RESET_CRC(hspi);}#endif /* USE_SPI_CRC *//* Check if the SPI is already enabled */if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE){/* Enable SPI peripheral */__HAL_SPI_ENABLE(hspi);}/* Transmit and Receive data in 16 Bit mode */if (hspi->Init.DataSize == SPI_DATASIZE_16BIT){if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U)){hspi->Instance->DR = *((uint16_t *)hspi->pTxBuffPtr);hspi->pTxBuffPtr += sizeof(uint16_t);hspi->TxXferCount--;}while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U)){/* Check TXE flag */if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U)){hspi->Instance->DR = *((uint16_t *)hspi->pTxBuffPtr);hspi->pTxBuffPtr += sizeof(uint16_t);hspi->TxXferCount--;/* Next Data is a reception (Rx). Tx not allowed */txallowed = 0U;#if (USE_SPI_CRC != 0U)/* Enable CRC Transmission */if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)){SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);}#endif /* USE_SPI_CRC */}/* Check RXNE flag */if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U)){*((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;hspi->pRxBuffPtr += sizeof(uint16_t);hspi->RxXferCount--;/* Next Data is a Transmission (Tx). Tx is allowed */txallowed = 1U;}if (((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)){errorcode = HAL_TIMEOUT;goto error;}}}/* Transmit and Receive data in 8 Bit mode */else{if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U)){*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);hspi->pTxBuffPtr += sizeof(uint8_t);hspi->TxXferCount--;}while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U)){/* Check TXE flag */if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U)){*(__IO uint8_t *)&hspi->Instance->DR = (*hspi->pTxBuffPtr);hspi->pTxBuffPtr++;hspi->TxXferCount--;/* Next Data is a reception (Rx). Tx not allowed */txallowed = 0U;#if (USE_SPI_CRC != 0U)/* Enable CRC Transmission */if ((hspi->TxXferCount == 0U) && (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE)){SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);}#endif /* USE_SPI_CRC */}/* Wait until RXNE flag is reset */if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)) && (hspi->RxXferCount > 0U)){(*(uint8_t *)hspi->pRxBuffPtr) = hspi->Instance->DR;hspi->pRxBuffPtr++;hspi->RxXferCount--;/* Next Data is a Transmission (Tx). Tx is allowed */txallowed = 1U;}if ((((HAL_GetTick() - tickstart) >= Timeout) && ((Timeout != HAL_MAX_DELAY))) || (Timeout == 0U)){errorcode = HAL_TIMEOUT;goto error;}}}#if (USE_SPI_CRC != 0U)/* Read CRC from DR to close CRC calculation process */if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE){/* Wait until TXE flag */if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK){/* Error on the CRC reception */SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);errorcode = HAL_TIMEOUT;goto error;}/* Read CRC */READ_REG(hspi->Instance->DR);}/* Check if CRC error occurred */if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR)){SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);/* Clear CRC Flag */__HAL_SPI_CLEAR_CRCERRFLAG(hspi);errorcode = HAL_ERROR;}#endif /* USE_SPI_CRC *//* Check the end of the transaction */if (SPI_EndRxTxTransaction(hspi, Timeout, tickstart) != HAL_OK){errorcode = HAL_ERROR;hspi->ErrorCode = HAL_SPI_ERROR_FLAG;goto error;}/* Clear overrun flag in 2 Lines communication mode because received is not read */if (hspi->Init.Direction == SPI_DIRECTION_2LINES){__HAL_SPI_CLEAR_OVRFLAG(hspi);}error :hspi->State = HAL_SPI_STATE_READY;__HAL_UNLOCK(hspi);return errorcode;}On the topic of the J-Link, what exactly does it help with? The breakpoints and single stepping works on with the ST-link. Obviously it is useless for anything real time because the program grinds to a halt while you are single stepping.