32F417 SPI Master Mode 1-byte-case mystery - Page 1
Forum section: electronics / general

author: peter-h
author_rank: Super Contributor
timestamp: August 18, 2021, 02:14:35 pm
content: This is a bit of HAL code which is generated by the Cube code generator, for Master transmit and receive.With SPI you always receive a byte for each one you transmit, but you can do TX-only by discarding the RX data, or do RX-only by TXing a dummy byte.It is bloated as usual but I am reproducing the whole thing: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;}The code fragment in question is thisCode: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--;}What is the point of it? It runs only if the block length parameter is 1 byte. I have been reading the RM (around page 890) but can't see why the 1-byte-only case should be treated differently. Note that SPI_MODE_SLAVE=false always.Surely the loop below itCode: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;}/* 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;}(with the CRC bit removed for clarity) should do?But if I remove the 1 byte case test it does not work! And Iamdoing lots of 1 byte transfers. The 1 byte case is a special case of what they call a "discontinuous transfer".The SPI stuff is polled, but interrupts are running in the background so a continuous block transfer is impossible anyway unless you disable interrupts for the entire block (usually 512 bytes).

author: thm_w
author_rank: Super Contributor
timestamp: August 19, 2021, 12:20:37 am
content: Why not leave it as is instead of trying to optimize it?If you are SendReceiving one byte then the function knows you only need to transmit.If you are SendReceiving two bytes then the function needs to transmit and then receive.If you SendReceive one byte and remove the logic to handle that case, its going to set txallowed to 0, and screw up the flow of the function.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 19, 2021, 12:54:20 am
content: As it's a blocking function, if you follow the flow of exec., even for the 1 byte case, it will wait until the corresponding RX byte has been received.Yes there is this 'txallowed' flag, but if you follow the 1-byte case, it should not make a difference (as 'RxXferCount' will also be 1). The only real difference I see is that the second TX part checks for the TXE flag, while the first TX version for the 1-byte case doesn't.Now there's something I didn't understand here, but that sounds fishy:QuoteI don't understand what you're doing exactly, and what this interrupt is all about. Why use a blocking call for the transmit and use interrupts at the same time? There's an interrupt version for SPI transmit in the HAL that will do the job for you. Maybe you should post the interrupt code so that we can understand what you're doing.

author: peter-h
author_rank: Super Contributor
timestamp: August 19, 2021, 06:36:42 am
content: Sorry for the confusion; there are noSPIinterrupts running. My comment about interrupts was regarding the transfers always being potentially "discontinuous". The RM discusses two cases: continuous (where the transmit buffer is always kept going) and discontinuous. If one has background ints running (e.g. a 1kHz timer, and RTOS) then the only way to guarantee continuous operation is to use DMA.Yes; the function is blocking, because it loops around until TX is finished and RX is finished, but it has to because if you want to collect the received byte you have no option but to hang around for it to fully arriveAnd there will always be a received byte for every one you transmit - that is how SPI works.The 1 byte case does not need to check TXE because the SPI is double buffered (one tx holding reg and one tx shift reg) so the single byte will always be able to be inserted. In fact if you got the timing right (not too fast) you should be able to transmittwobytes and then return, non-blocking.I am not bothered about removing that 1-byte case; I am trying to understand why it is needed to be treated separately.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 19, 2021, 04:52:36 pm
content: Quote from: peter-h on August 19, 2021, 06:36:42 amOK. But we still don't know exactly what you're doing. Can you not transmit blocks of data by more than 1 byte? Doing 1 byte per 1 byte transfers is going to be very inefficient.Quote from: peter-h on August 19, 2021, 06:36:42 amLooks like you answered this yourself. It treats the case separately because it doesn't need to check TXE - so I guess that slightly more efficient, although I would expect it to make unsignificant difference given how much code has to be executed before this in the function anyway... And, this assumes that if you access SPI through this blocking transmit function, you will never access it any other way. Because otherwise, the internal buffer could actually be full when you call the function... if you have sent data to it in another way before calling this function.The point is still: what do you mean by "not working" when you remove the 1-byte case?As we saw, the only difference seems to be that it doesn't check TXE, so if the function doesn't transmit anything when you remove this case, we can assume that TXE would never be '1' when you call the function. How can that be possible? I suspect you have a problem elsewhere in your code. If you're sure you don't, then it might be because of a silicon bug (errata), but I'm not sure I can see what it could be. Obviously we need more details to see what's wrong here.

author: peter-h
author_rank: Super Contributor
timestamp: August 20, 2021, 10:29:10 am
content: "Can you not transmit blocks of data by more than 1 byte? Doing 1 byte per 1 byte transfers is going to be very inefficient."It is working fine. I am just trying to find out why the len=1 case is treated specially."It treats the case separately because it doesn't need to check TXE - so I guess that slightly more efficient"That doesn't appear to be the case given that that test always falls through to the loop which tests both TX and RX ready flags.If the 1 byte case put the byte in andexited(and some other means was used to prevent TX buffer overrun) but actually it always falls through to the loop below it.I think this is a "silicon thing"But the RM is silent on it.

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 20, 2021, 05:33:58 pm
content: Quote from: peter-h on August 20, 2021, 10:29:10 amThe 1-byte case writes the byte to send to the TX buffer without checking the TXE flag.Then of course it proceeds to the code below, since there is no return here. But in this case, only the test checking that the RX byte has been received matters, because this one:Code:if ((__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)) && (hspi->TxXferCount > 0U) && (txallowed == 1U))is guaranteed to fail. ('hspi->TxXferCount' will be 0 at this point, because it is decremented above.)Now the order of the tests in this 'if' clause is questionable. Because of the '&&' operators, the test for TXE is normally guaranteed to always be executed, even if the others ('hspi->TxXferCount > 0U' and 'txallowed == 1U') are false. Putting those two first would avoid having to test for TXE when it's not needed. Since the __HAL_SPI_GET_FLAG() macro is probably a volatile access, it's always going to be executed even if the compiler can optimize the 1-byte case specifically. Looks wasteful.Have you actually tried debugging this just to see what it was executing exactly in the 1-byte case with your modification?Quote from: peter-h on August 20, 2021, 10:29:10 amYes, because as I just said, it has to execute the wait for the RX byte anyway. This is a blocking call. The function will exit only if there's an error or if the transmission is completed.Quote from: peter-h on August 20, 2021, 10:29:10 amYou still didn't quite tell us what it was doing exactly when you said "it doesn't work". Or maybe you should post your exact modified code, as it may not be what we think it is. I assumed you just removed the ' if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U)) { ... }' part, and following the code, the only difference with the remaining part would be that without it, the byte to send would be written to the corresponding register without checking for TXE. The rest looks the same to me.I doubt a silicon bug per se, as the almost same code can be found for the L4 family (I checked). Unless it were a silicon bug affecting all STM32 '4' series. But still, I don't quite know what happens and what you noticed.The peripheral can't know in advance how many bytes you're going to send. So if there was a bug here, the case for > 1 byte blocks would never send the first one either. Again there's probably some information missing here.

author: peter-h
author_rank: Super Contributor
timestamp: August 20, 2021, 07:59:14 pm
content: My simplified version (no timer tick needed) is hereCode:HAL_StatusTypeDef B_HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size){uint16_t initial_TxXferCount;uint32_t tmp_mode;HAL_SPI_StateTypeDef tmp_state;/* Variable used to alternate Rx and Tx during transfer */uint32_t txallowed = 1U;HAL_StatusTypeDef errorcode = HAL_OK;/* 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;/* Check if the SPI is already enabled */if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE){__HAL_SPI_ENABLE(hspi);}/* Transmit and Receive data in 8 Bit mode */// The need for this initial byte is unknownif ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U)){*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);hspi->pTxBuffPtr++;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;}/* 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;}}/* 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;return errorcode;}It is hard to track this stuff down. I commented-out the len=0 bit and called the function once, and it appeared to run once, under debug, ok. But it failed to run on later calls, getting stuck in the loopCode:while ((hspi->TxXferCount > 0U) || (hspi->RxXferCount > 0U)){So the mystery continues. That code is there for a reason. I am not sure what I can easily do given that this thing runs at 21mbps and there is no way to do a real time trace on it. Also it clearly does work as published so this is of passing interest only.BTW, I thought that with multiple conditionals, the compiler is free to evaluate them in any order and is free to abandon further tests as soon as the state of the whole expression in overall brackets is determined.

author: NorthGuy
author_rank: Super Contributor
timestamp: August 20, 2021, 09:28:13 pm
content: Quote from: peter-h on August 20, 2021, 07:59:14 pmIf you mean "&&" then no. There's a sequence point. Left first, then right.Moreover, if the left side is 0, the right side must not be evaluated (functions not called, ++ not advanced etc.).Same with ||. If the left side is 1, the right side is not evaluated. For example:Code:_do() || _die();If _do() returns one, die() is not called.

author: peter-h
author_rank: Super Contributor
timestamp: August 21, 2021, 08:57:37 am
content: In a lot of testing, I have found a problem with this codeCode:// Wait for last byte to get shifted out - needed before CS is raised!// Two tests done as per the RM textwhile (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE)==1) {}// wait for tx buffer emptywhile (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_BSY)==0) {}// wait for tx shift reg emptyIt is a) per the RM and b) per common sense re how an SPI "USART" should work, but every few hundred k calls it hangs, on the first line. It is as if the SPI clock was stopped so the TX never goes empty / never stops being busy.And sure enough the ST code implements only the 2nd line - via the mega convoluted route ofCode:/*** @brief Handle the check of the RX transaction complete.* @param hspi pointer to a SPI_HandleTypeDef structure that contains* the configuration information for SPI module.* @param Timeout Timeout duration* @param Tickstart tick start value* @retval HAL status*/static HAL_StatusTypeDef SPI_EndRxTransaction(SPI_HandleTypeDef *hspi, uint32_t Timeout, uint32_t Tickstart){if ((hspi->Init.Mode == SPI_MODE_MASTER) && ((hspi->Init.Direction == SPI_DIRECTION_1LINE)|| (hspi->Init.Direction == SPI_DIRECTION_2LINES_RXONLY))){/* Disable SPI peripheral */__HAL_SPI_DISABLE(hspi);}/* Control the BSY flag */if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_BSY, RESET, Timeout, Tickstart) != HAL_OK){SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_FLAG);return HAL_TIMEOUT;}return HAL_OK;}/*** @brief Handle SPI Communication Timeout.* @param hspi pointer to a SPI_HandleTypeDef structure that contains* the configuration information for SPI module.* @param Flag SPI flag to check* @param State flag state to check* @param Timeout Timeout duration* @param Tickstart tick start value* @retval HAL status*/static HAL_StatusTypeDef SPI_WaitFlagStateUntilTimeout(SPI_HandleTypeDef *hspi, uint32_t Flag, FlagStatus State,uint32_t Timeout, uint32_t Tickstart){while ((__HAL_SPI_GET_FLAG(hspi, Flag) ? SET : RESET) != State){if (Timeout != HAL_MAX_DELAY){if (((HAL_GetTick() - Tickstart) >= Timeout) || (Timeout == 0U)){/* Disable the SPI and reset the CRC: the CRC value should be clearedon both master and slave sides in order to resynchronize the masterand slave for their respective CRC calculation *//* Disable TXE, RXNE and ERR interrupts for the interrupt process */__HAL_SPI_DISABLE_IT(hspi, (SPI_IT_TXE | SPI_IT_RXNE | SPI_IT_ERR));if ((hspi->Init.Mode == SPI_MODE_MASTER) && ((hspi->Init.Direction == SPI_DIRECTION_1LINE)|| (hspi->Init.Direction == SPI_DIRECTION_2LINES_RXONLY))){/* Disable SPI peripheral */__HAL_SPI_DISABLE(hspi);}/* Reset CRC Calculation */if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE){SPI_RESET_CRC(hspi);}hspi->State = HAL_SPI_STATE_READY;/* Process Unlocked */__HAL_UNLOCK(hspi);return HAL_TIMEOUT;}}}return HAL_OK;}which, once you work through it, is just a long winded way of waiting for the SPI BUSY flag to disappear! However, there is clearly still a timing issue (probably concealed by the vast amount of ST code above, just to check the BUSY flag, with a timeout) and I found replacing the two lines with thisCode:// Best way to make sure TX is finished is by waiting for the RX byte (which is garbage) to arrivewhile (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)){// garbage is unused so call it 'volatile' to make sure it isn't optimised outvolatile uint8_t garbage = hspi->Instance->DR;}fixes it.I haven't found a simple way to stop the compiler complaining that 'garbage' is not used, although there are obvious ones like declaring it extern.IOW, the "spi transmit" function is now same as the "spi transmit-receive" function, except we throw away the received byte(s) while waiting for them all to arrive.The clue to this was that, running it under debug, it always got stuck on one of the two lines, but using the SPI via the USB filesystem worked 100% but that only uses the transmit-receive function, whereas my internal stuff called the transmit-only as well. The debug confirmed this.I think at least a part of the issue is something to do with the time it takes for status flags to get changed after some action is done. Let's say they use PCLK2/2 to drive the SPI flags. Then there will be a 50ns delay (with a 42MHz PCLK which I bet is what most use). Just like with that ETH bug where DIV8 or DIV16 on PCLK2 breaks ETH (see other thread). This 50ns delay will get exposed once code bloat is taken out and some optimisation is done, on a 168MHz CPU. That said, I saw the issue with -O0 as well. I saw ST make some 400MHz versions of these CPUs and I bet a lot more breaks on those!Note that this serial FLASH SPI code always runs in 2LINE mode i.e. the receiver is enabled even in the TX-only function. I suspect the above code (waiting for the garbage byte to arrive) would not work in 1LINE Master mode, which I would expect would disable the RX.

author: newbrain
author_rank: Super Contributor
timestamp: August 21, 2021, 09:23:22 am
content: Quote from: peter-h on August 21, 2021, 08:57:37 amIf you don't want warning about garbage, then don't put garbage in your code!(pun intended)Code:volatile struct { unsigned int DR; } *SPI = (void *)0x80000000;void empty_read(void){SPI->DR;}No garbage here, andno warnings with -Wall -Wextra -pedantc -std=c11Slightly longer explanation: the compiler must emit code for the read to honour the volatile qualifier, so you are guaranteed (as discussed at length in the other thread) against over eager optimization.For the same 'volatile' reason, the compilerknowsthere's a side effect to this statement though it apparently has no effect, and hence will suppress the "code has no effect" warning.About the &&/|| operators and sequencing (refraining once again to quote theholy bookstandard): please, take the time to get to know C from a good book - I have noticed you have a good grasp and will to learn, but still some shaky basics.

author: peter-h
author_rank: Super Contributor
timestamp: August 21, 2021, 09:29:52 am
content: Yeah I would like to know how to read hspi->Instance->DR without declaring pointersDR is already declared volatile, in the ST .h files, with __IO.

author: newbrain
author_rank: Super Contributor
timestamp: August 21, 2021, 09:36:40 am
content: Quote from: peter-h on August 21, 2021, 09:29:52 amEhm, you don't need to declare any more pointers: I just used that 'SPI' just as an example in my snippet for something vaguely similar to what you have.Just remove the garbage!Code:hspi->Instance->DR ;is all you need.

author: peter-h
author_rank: Super Contributor
timestamp: August 21, 2021, 10:04:28 am
content: I am amazed that will actually read DR, without it being an assignment to something. Learn something every dayBut, yes, just checked the asm code.

author: newbrain
author_rank: Super Contributor
timestamp: August 21, 2021, 10:44:42 am
content: Quote from: peter-h on August 21, 2021, 10:04:28 amWell, that statement is a well formed expression, using the the postfix operator '->' (twice).An expression in C is:Quote from: C11 standard, 6.5 Expression, §1 (emphasis mine)In this simple case, is just a computation.As no assignment, or e.g. increment, operator has been used, there are no explicit side effects (such as changing a value stored in memory) and the compiler would, in general, be free to remove it -and it usually does, with a warning*.The volatile type qualifier forces the computation to really happen, as also a read access to a volatile qualified object is considered a visible side effect.Further references in C11 (I did it again...):Quote from: C11 standard, 6.7.3 Type qualifiers, §7And:Quote from: C11 standard, 5.1.2.3 Program execution, §2BTW: a draft of the standard (inconsequential differences with the final approved version) is publicly availablehere.*the warning can usually be suppressed by casting the expression to void, in our case it does not make a difference.This is just idiomatic, and not completely reliable as it depends on the compiler and its version: one is telling the compiler that the value returned by the expression is effectively thrown away, but that information was already contained in the statements, the redundancy make the compiler understand one really means it.

author: peter-h
author_rank: Super Contributor
timestamp: August 21, 2021, 01:32:51 pm
content: Yes; this works. I can't break it now.Clearly the safest way to make a "blocking" SPI transmit function is to enable 2LINES mode (TXand RX) and wait for the received byte(s) to all arrive (and take them all out). That seems a lot more robust than checking the status flags.The funny thing is that if you wait for the RX data to all arrive, any tests you are doing, with complicated timeouts, for the SPI peripheral to become READY, are wasted, because it always will be ready.Just like 2-wire RS485 in the old daysTurn the driver on, stuff the data into the UART, wait for the RX stuff to arrive, and when it has, turn the driver off.On a related matter, can anyone see that the timeout based on HAL_GetTick is dodgy, if you happen to be testing it just as the 32 bit counter (of milliseconds) wraps around?Code:/*** @brief 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 pData pointer to data buffer* @param Size amount of data to be received* @param Timeout Timeout duration* @retval HAL status*/HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout){uint32_t tickstart;HAL_StatusTypeDef errorcode = HAL_OK;if ((hspi->Init.Mode == SPI_MODE_MASTER) && (hspi->Init.Direction == SPI_DIRECTION_2LINES)){hspi->State = HAL_SPI_STATE_BUSY_RX;/* Call transmit-receive function to send Dummy data on Tx line and generate clock on CLK line */return HAL_SPI_TransmitReceive(hspi, pData, pData, Size, Timeout);}/* Process Locked */__HAL_LOCK(hspi);/* Init tickstart for timeout management*/tickstart = HAL_GetTick();if (hspi->State != HAL_SPI_STATE_READY){errorcode = HAL_BUSY;goto error;}if ((pData == NULL) || (Size == 0U)){errorcode = HAL_ERROR;goto error;}/* Set the transaction information */hspi->State = HAL_SPI_STATE_BUSY_RX;hspi->ErrorCode = HAL_SPI_ERROR_NONE;hspi->pRxBuffPtr = (uint8_t *)pData;hspi->RxXferSize = Size;hspi->RxXferCount = Size;/*Init field not used in handle to zero */hspi->pTxBuffPtr = (uint8_t *)NULL;hspi->TxXferSize = 0U;hspi->TxXferCount = 0U;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);/* this is done to handle the CRCNEXT before the latest data */hspi->RxXferCount--;}#endif /* USE_SPI_CRC *//* Configure communication direction: 1Line */if (hspi->Init.Direction == SPI_DIRECTION_1LINE){SPI_1LINE_RX(hspi);}/* Check if the SPI is already enabled */if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE){/* Enable SPI peripheral */__HAL_SPI_ENABLE(hspi);}/* Receive data in 8 Bit mode */if (hspi->Init.DataSize == SPI_DATASIZE_8BIT){/* Transfer loop */while (hspi->RxXferCount > 0U){/* Check the RXNE flag */if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)){/* read the received data */(* (uint8_t *)hspi->pRxBuffPtr) = *(__IO uint8_t *)&hspi->Instance->DR;hspi->pRxBuffPtr += sizeof(uint8_t);hspi->RxXferCount--;}else{/* Timeout management */if ((((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U)){errorcode = HAL_TIMEOUT;goto error;}}}}else{/* Transfer loop */while (hspi->RxXferCount > 0U){/* Check the RXNE flag */if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_RXNE)){*((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;hspi->pRxBuffPtr += sizeof(uint16_t);hspi->RxXferCount--;}else{/* Timeout management */if ((((HAL_GetTick() - tickstart) >= Timeout) && (Timeout != HAL_MAX_DELAY)) || (Timeout == 0U)){errorcode = HAL_TIMEOUT;goto error;}}}}#if (USE_SPI_CRC != 0U)/* Handle the CRC Transmission */if (hspi->Init.CRCCalculation == SPI_CRCCALCULATION_ENABLE){/* freeze the CRC before the latest data */SET_BIT(hspi->Instance->CR1, SPI_CR1_CRCNEXT);/* Read the latest data */if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK){/* the latest data has not been received */errorcode = HAL_TIMEOUT;goto error;}/* Receive last data in 16 Bit mode */if (hspi->Init.DataSize == SPI_DATASIZE_16BIT){*((uint16_t *)hspi->pRxBuffPtr) = (uint16_t)hspi->Instance->DR;}/* Receive last data in 8 Bit mode */else{(*(uint8_t *)hspi->pRxBuffPtr) = *(__IO uint8_t *)&hspi->Instance->DR;}/* Wait the CRC data */if (SPI_WaitFlagStateUntilTimeout(hspi, SPI_FLAG_RXNE, SET, Timeout, tickstart) != HAL_OK){SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);errorcode = HAL_TIMEOUT;goto error;}/* Read CRC to Flush DR and RXNE flag */READ_REG(hspi->Instance->DR);}#endif /* USE_SPI_CRC *//* Check the end of the transaction */if (SPI_EndRxTransaction(hspi, Timeout, tickstart) != HAL_OK){hspi->ErrorCode = HAL_SPI_ERROR_FLAG;}#if (USE_SPI_CRC != 0U)/* Check if CRC error occurred */if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_CRCERR)){SET_BIT(hspi->ErrorCode, HAL_SPI_ERROR_CRC);__HAL_SPI_CLEAR_CRCERRFLAG(hspi);}#endif /* USE_SPI_CRC */if (hspi->ErrorCode != HAL_SPI_ERROR_NONE){errorcode = HAL_ERROR;}error :hspi->State = HAL_SPI_STATE_READY;__HAL_UNLOCK(hspi);return errorcode;}The counter, assuming it starts at zero, will wrap 49 days after the product is powered-up, and you will then get a spurious "timeout" error condition.

author: abyrvalg
author_rank: Frequent Contributor
timestamp: August 21, 2021, 02:00:53 pm
content: There is no problem wrapping around an uint32_t as far as you are subtracting now-start. 0-0xFFFFFFFF = 1 and so on. You need those 49 days to pass between now and start of a single transfer to get a problem.

author: newbrain
author_rank: Super Contributor
timestamp: August 21, 2021, 02:46:22 pm
content: Quote from: peter-h on August 21, 2021, 01:32:51 pmUnsigned integers have a well defined wraparound semantics, as abyrvalg said.Quote from: C11 standard, 6.2.5 Types §9This would definitely be wrong instead:Code:if (HAL_GetTick() > tickstart + Timeout) ...E.g, using 16 bit unsigned for clarity:tickstart = 0xFFF0uTimeout = 0x0020ucurrent tick value = 0xFFFFuRight code (as in HAL):Hal_GetTick() - tickstart = 0xFFFFu - 0xFFF0u = 0x000Fu, which is less than Timeout, so, not expired.Wrong code:tickstart + Timeout = 0xFFF0u + 0x0020u = 0x0010u, so Hal_GetTick() is greater, and we have a spurious time out.Now the feeling has become almost a certainty: you need some good C learning material.Unfortunately, apart from the now surpassed (by the evolution of the language) K&R ANSI version, I would not know what to recommend.

author: peter-h
author_rank: Super Contributor
timestamp: August 21, 2021, 02:57:02 pm
content: This?https://www.amazon.co.uk/ARM-Assembly-Language-Programming-Architecture/dp/0997925906Don't worry; I am a hopeless causeThe plus is that I don't usually ask the same question twice. Then I will be ready for a care home.One day when I have nothing better to do I will investigate that 1-byte case. It probably covers some hardware "feature".

author: SiliconWizard
author_rank: Super Contributor
timestamp: August 21, 2021, 05:32:45 pm
content: Unfortunately, as newbrain said, there's no really good material for learning modern C, and as often discussed in this forum, C is rarely well taught either. So don't feel bad.I can suggest getting ahold of the C standard and consulting it when in doubt. While the phrasing is sometimes less than ideal, it's the reference spec and you're likely to learn a few things you had no idea about while reading it. Also, each new std revision brings improvements on how things are defined. The official std is not free and kind of expensive, but you can find final drafts easily (and they are almost indentical to the official releases AFAIK.) Wikipedia gives a link to the final draft of C17:https://web.archive.org/web/20181230041359/http://www.open-std.org/jtc1/sc22/wg14/www/abq/c17_updated_proposed_fdis.pdf

author: wek
author_rank: Frequent Contributor
timestamp: August 23, 2021, 10:11:15 am
content: Quote from: peter-h on August 21, 2021, 08:57:37 amCan you please explain, what is this piece of code intended to do?JW

author: peter-h
author_rank: Super Contributor
timestamp: August 23, 2021, 01:22:20 pm
content: It implements what the RM requires, to produce a "blocking" SPI transmit (transmit only, not interested in what comes back) blocking function.Why would you want a blocking function?1) The next SPI call will need to make sure the SPI is not busy otherwise2) A blocking transmit is the easiest way to raise CS at the right point (not too early).On the last point, there is an "auto CS" feature but many have struggled with it, and it is basically useless for many SPI slave chips because it sets CS=1 in between bytes, unless you are loading up the SPI TX solidly, which in the presence of an RTOS etc implies DMA.

author: wek
author_rank: Frequent Contributor
timestamp: August 23, 2021, 03:04:12 pm
content: Quote from: peter-h on August 23, 2021, 01:22:20 pmYou are probably referring to theHandling data transmission and receptionsection of the SPI chapter of RM0090. The last item in the sequence there says:Quoteexcept that you want to raise a CS instead of disabling SPI, but that's unimportant detail.I read the above sentence asCode:while ((SPI1->SR & SPI_SR_TXE) == 0);while ((SPI1->SR & SPI_SR_BSY) != 0);which is sort of opposite of what you've written - unless the HALxxx macros work differently than I thought.QuoteYou are probably referring to what ST calls "NSS pulse mode" (see SPIx_CR2.NSSP), but that's not available in 'F4, only in newer families.JW

author: peter-h
author_rank: Super Contributor
timestamp: August 23, 2021, 05:53:21 pm
content: "except that you want to raise a CS instead of disabling SPI, but that's unimportant detail."Disabling SPI seems pointless - unless you perhaps want to reassign the I/O pins, in a scenario like dealing with the stupid delta-sigma ADCs like the ADS1118/MCP3550 which use the same wire (SDO) for returning SPI data and for indicating "conversion complete"Or maybe when changing the clock speed (sharing the same SPI channel with multiple different slaves).My boot block SPI2 -> serial FLASH SPI code certainly never disables it."You are probably referring to the Handling data transmission and reception section of the SPI chapter of RM0090"Yes."You are probably referring to what ST calls "NSS pulse mode" (see SPIx_CR2.NSSP), but that's not available in 'F4, only in newer families."Page 876+ of the RM. Never realised it doesn't apply to 32F417... are you sure?

author: wek
author_rank: Frequent Contributor
timestamp: August 23, 2021, 08:17:58 pm
content: I did not intend to discuss merits of CS/NSS/SPI disable - that's just any arbitrary action after SPI transmission is finished.And what I said was, that your twoliner above is probably wrong.Quote from: meQuote from: youYes.JW

author: peter-h
author_rank: Super Contributor
timestamp: August 24, 2021, 07:56:34 am
content: I played around with that stuff but as noted above I eventually replaced it with a "wait to receive" which works perfectly.With SPI, there is no point in a "transmit only" function, unless you are- sending just 1 byte and want anon-blockingfunction so you can get right back to do something else (but in most scenarios the byte sent was a selector of some sort and you then need to do more of the same anyway, and then you have to wait for the SPI to finish that previous byte)- sending more than 1 byte and using interrupts or DMA to feed the SPI transmitterand you still need to get back at some point to raise CS (I guess most people would use a TX-empty interrupt to do that).Yes, the NSS seems useless and a lot of people have concluded that. The inherent problem is that the SPI subsystem is not aware of the higher level objective which normally involves multiple transfers, all done with CS=0, and if you raise CS briefly you bugger up the device state. The way to do auto-CS properly would be with a byte counter and then the SPI circuit could drop CS before the start of the clock and raise it only when the byte count = 0andthe last byte been shifted out. Then you could feed the SPI with DMA (or TX Empty interrupts), involve little or no CPU code, and have CS=1 happening automatically.This is different from 2-wire RS485 where it is legitimate to enable the driver only for each byte, even if it gets disabled for some fraction of a bit time in between bytes. Accordingly, some UARTs offer "auto driver enable".