diff --git a/targets/TARGET_STM/stm_spi_api.c b/targets/TARGET_STM/stm_spi_api.c index 3c10bdaf3d9..44b6d13d966 100644 --- a/targets/TARGET_STM/stm_spi_api.c +++ b/targets/TARGET_STM/stm_spi_api.c @@ -30,6 +30,8 @@ #include "mbed_assert.h" #include "mbed_error.h" #include "mbed_debug.h" +#include "mbed_critical.h" +#include "mbed_wait_api.h" #include "spi_api.h" #if DEVICE_SPI @@ -79,6 +81,17 @@ extern HAL_StatusTypeDef HAL_SPIEx_FlushRxFifo(SPI_HandleTypeDef *hspi); #define HAS_32BIT_SPI_TRANSFERS 1 #endif // SPI_DATASIZE_X +/** + * Flush RX FIFO/input register of SPI interface and clear overrun flag. + */ +static inline void spi_flush_rx(spi_t *obj) +{ +#if defined(SPI_FLAG_FRLVL) + HAL_SPIEx_FlushRxFifo(&(SPI_S(obj)->handle)); +#endif + LL_SPI_ClearFlag_OVR(SPI_INST(obj)); +} + void spi_get_capabilities(PinName ssel, bool slave, spi_capabilities_t *cap) { if (slave) { @@ -136,6 +149,9 @@ void init_spi(spi_t *obj) if (HAL_SPI_Init(handle) != HAL_OK) { error("Cannot initialize SPI"); } + /* In some cases after SPI object re-creation SPI overrun flag may not + * be cleared, so clear RX data explicitly to prevent any transmissions errors */ + spi_flush_rx(obj); /* In case of standard 4 wires SPI,PI can be kept enabled all time * and SCK will only be generated during the write operations. But in case * of 3 wires, it should be only enabled during rd/wr unitary operations, @@ -602,6 +618,50 @@ static const uint32_t baudrate_prescaler_table[] = {SPI_BAUDRATEPRESCALER_2, SPI_BAUDRATEPRESCALER_256 }; +/** + * Convert SPI_BAUDRATEPRESCALER_ constant into numeric prescaler rank. + */ +static uint8_t spi_get_baudrate_prescaler_rank(uint32_t value) +{ + switch (value) { + case SPI_BAUDRATEPRESCALER_2: + return 0; + case SPI_BAUDRATEPRESCALER_4: + return 1; + case SPI_BAUDRATEPRESCALER_8: + return 2; + case SPI_BAUDRATEPRESCALER_16: + return 3; + case SPI_BAUDRATEPRESCALER_32: + return 4; + case SPI_BAUDRATEPRESCALER_64: + return 5; + case SPI_BAUDRATEPRESCALER_128: + return 6; + case SPI_BAUDRATEPRESCALER_256: + return 7; + default: + return 0xFF; + } +} + +/** + * Get actual SPI baudrate. + * + * It may differ from a value that is passed to the ::spi_frequency function. + */ +int spi_get_baudrate(spi_t *obj) +{ + struct spi_s *spiobj = SPI_S(obj); + SPI_HandleTypeDef *handle = &(spiobj->handle); + + int freq = spi_get_clock_freq(obj); + uint8_t baudrate_rank = spi_get_baudrate_prescaler_rank(handle->Init.BaudRatePrescaler); + MBED_ASSERT(baudrate_rank != 0xFF); + return freq >> (baudrate_rank + 1); +} + + void spi_frequency(spi_t *obj, int hz) { struct spi_s *spiobj = SPI_S(obj); @@ -777,13 +837,227 @@ static inline int datasize_to_transfer_bitshift(uint32_t DataSize) } } +/** + * Check if SPI master interface is writable. + * + * @param obj + * @return 0 - SPI isn't writable, non-zero - SPI is writable + */ +static inline int msp_writable(spi_t *obj) +{ +#if TARGET_STM32H7 + return (int)LL_SPI_IsActiveFlag_TXP(SPI_INST(obj)); +#else /* TARGET_STM32H7 */ + return (int)LL_SPI_IsActiveFlag_TXE(SPI_INST(obj)); +#endif /* TARGET_STM32H7 */ +} + +/** + * Check if SPI master interface is readable. + * + * @param obj + * @return 0 - SPI isn't readable, non-zero - SPI is readable + */ +static inline int msp_readable(spi_t *obj) +{ +#if TARGET_STM32H7 + return (int)LL_SPI_IsActiveFlag_RXP(SPI_INST(obj)); +#else /* TARGET_STM32H7 */ + return (int)LL_SPI_IsActiveFlag_RXNE(SPI_INST(obj)); +#endif /* TARGET_STM32H7 */ +} + +/** + * Wait till SPI master interface is writable. + */ +static inline void msp_wait_writable(spi_t *obj) +{ + while (!msp_writable(obj)); +} + +/** + * Wait till SPI master interface is readable. + */ +static inline void msp_wait_readable(spi_t *obj) +{ + while (!msp_readable(obj)); +} + +/** + * Check if SPI master interface is busy. + * + * @param obj + * @return 0 - SPI isn't busy, non-zero - SPI is busy + */ +static inline int msp_busy(spi_t *obj) +{ +#if TARGET_STM32H7 + return !(int)LL_SPI_IsActiveFlag_TXC(SPI_INST(obj)); +#else /* TARGET_STM32H7 */ + return (int)LL_SPI_IsActiveFlag_BSY(SPI_INST(obj)); +#endif /* TARGET_STM32H7 */ +} + +/** + * Wait till SPI master interface isn't busy. + */ +static inline void msp_wait_not_busy(spi_t *obj) +{ + while (msp_busy(obj)); +} + +/** + * Write data to SPI master interface. + */ +static inline void msp_write_data(spi_t *obj, int value, int bitshift) +{ + if (bitshift == 1) { + LL_SPI_TransmitData16(SPI_INST(obj), (uint16_t)value); +#ifdef HAS_32BIT_SPI_TRANSFERS + } else if (bitshift == 2) { + LL_SPI_TransmitData32(SPI_INST(obj), (uint32_t)value); +#endif /* HAS_32BIT_SPI_TRANSFERS */ + } else { + LL_SPI_TransmitData8(SPI_INST(obj), (uint8_t)value); + } +} + +/** + * Read data from SPI master interface. + */ +static inline int msp_read_data(spi_t *obj, int bitshift) +{ + if (bitshift == 1) { + return LL_SPI_ReceiveData16(SPI_INST(obj)); +#ifdef HAS_32BIT_SPI_TRANSFERS + } else if (bitshift == 2) { + return LL_SPI_ReceiveData32(SPI_INST(obj)); +#endif /* HAS_32BIT_SPI_TRANSFERS */ + } else { + return LL_SPI_ReceiveData8(SPI_INST(obj)); + } +} + +/** + * Transmit and receive SPI data in bidirectional mode. + * + * @param obj spi object + * @param tx_buffer byte-array of data to write to the device + * @param tx_length number of bytes to write, may be zero + * @param rx_buffer byte-array of data to read from the device + * @param rx_length number of bytes to read, may be zero + * @return number of transmitted and received bytes or negative code in case of error. + */ +static int spi_master_one_wire_transfer(spi_t *obj, const char *tx_buffer, int tx_length, + char *rx_buffer, int rx_length) +{ + struct spi_s *spiobj = SPI_S(obj); + SPI_HandleTypeDef *handle = &(spiobj->handle); + const int bitshift = datasize_to_transfer_bitshift(handle->Init.DataSize); + MBED_ASSERT(bitshift >= 0); + + /* Ensure that spi is disabled */ + LL_SPI_Disable(SPI_INST(obj)); + + /* Transmit data */ + if (tx_length) { + LL_SPI_SetTransferDirection(SPI_INST(obj), LL_SPI_HALF_DUPLEX_TX); +#if TARGET_STM32H7 + /* Set transaction size */ + LL_SPI_SetTransferSize(SPI_INST(obj), tx_length); +#endif /* TARGET_STM32H7 */ + LL_SPI_Enable(SPI_INST(obj)); +#if TARGET_STM32H7 + /* Master transfer start */ + LL_SPI_StartMasterTransfer(SPI_INST(obj)); +#endif /* TARGET_STM32H7 */ + + for (int i = 0; i < tx_length; i++) { + msp_wait_writable(obj); + msp_write_data(obj, tx_buffer[i], bitshift); + } + + /* Wait end of transaction */ + msp_wait_not_busy(obj); + + LL_SPI_Disable(SPI_INST(obj)); + +#if TARGET_STM32H7 + /* Clear transaction flags */ + LL_SPI_ClearFlag_EOT(SPI_INST(obj)); + LL_SPI_ClearFlag_TXTF(SPI_INST(obj)); + /* Reset transaction size */ + LL_SPI_SetTransferSize(SPI_INST(obj), 0); +#endif /* TARGET_STM32H7 */ + } + + /* Receive data */ + if (rx_length) { + LL_SPI_SetTransferDirection(SPI_INST(obj), LL_SPI_HALF_DUPLEX_RX); +#if TARGET_STM32H7 + /* Set transaction size and run SPI */ + LL_SPI_SetTransferSize(SPI_INST(obj), rx_length); + LL_SPI_Enable(SPI_INST(obj)); + LL_SPI_StartMasterTransfer(SPI_INST(obj)); + + /* Receive data */ + for (int i = 0; i < rx_length; i++) { + msp_wait_readable(obj); + rx_buffer[i] = msp_read_data(obj, bitshift); + } + + /* Stop SPI */ + LL_SPI_Disable(SPI_INST(obj)); + /* Clear transaction flags */ + LL_SPI_ClearFlag_EOT(SPI_INST(obj)); + LL_SPI_ClearFlag_TXTF(SPI_INST(obj)); + /* Reset transaction size */ + LL_SPI_SetTransferSize(SPI_INST(obj), 0); + +#else /* TARGET_STM32H7 */ + /* Unlike STM32H7 other STM32 families generates SPI Clock signal continuously in half-duplex receive mode + * till SPI is enabled. To stop clock generation a SPI should be disabled during last frame receiving, + * after generation at least one SPI clock cycle. It causes necessity of critical section usage. + * So the following consequences of steps is used to receive each byte: + * 1. Enter into critical section. + * 2. Enable SPI. + * 3. Wait one SPI clock cycle. + * 4. Disable SPI. + * 5. Wait full byte receiving. + * 6. Read byte. + * It gives some overhead, but gives stable byte reception without dummy reads and + * short delay of critical section holding. + */ + + /* get estimation about one SPI clock cycle */ + uint32_t baudrate_period_ns = 1000000000 / spi_get_baudrate(obj); + + for (int i = 0; i < rx_length; i++) { + core_util_critical_section_enter(); + LL_SPI_Enable(SPI_INST(obj)); + /* Wait single SPI clock cycle. */ + wait_ns(baudrate_period_ns); + LL_SPI_Disable(SPI_INST(obj)); + core_util_critical_section_exit(); + + msp_wait_readable(obj); + rx_buffer[i] = msp_read_data(obj, bitshift); + } + +#endif /* TARGET_STM32H7 */ + } + + return rx_length + tx_length; +} + int spi_master_write(spi_t *obj, int value) { struct spi_s *spiobj = SPI_S(obj); SPI_HandleTypeDef *handle = &(spiobj->handle); if (handle->Init.Direction == SPI_DIRECTION_1LINE) { - return HAL_SPI_Transmit(handle, (uint8_t *)&value, 1, TIMEOUT_1_BYTE); + int result = spi_master_one_wire_transfer(obj, (const char *)&value, 1, NULL, 0); + return result == 1 ? HAL_OK : HAL_ERROR; } const int bitshift = datasize_to_transfer_bitshift(handle->Init.DataSize); MBED_ASSERT(bitshift >= 0); @@ -806,44 +1080,15 @@ int spi_master_write(spi_t *obj, int value) #if TARGET_STM32H7 /* Master transfer start */ LL_SPI_StartMasterTransfer(SPI_INST(obj)); - - /* Wait TXP flag to transmit data */ - while (!LL_SPI_IsActiveFlag_TXP(SPI_INST(obj))); -#else - /* Wait TXE flag to transmit data */ - while (!LL_SPI_IsActiveFlag_TXE(SPI_INST(obj))); - -#endif /* TARGET_STM32H7 */ - - /* Transmit data */ - if (bitshift == 1) { - LL_SPI_TransmitData16(SPI_INST(obj), (uint16_t)value); -#ifdef HAS_32BIT_SPI_TRANSFERS - } else if (bitshift == 2) { - LL_SPI_TransmitData32(SPI_INST(obj), (uint32_t)value); #endif - } else { - LL_SPI_TransmitData8(SPI_INST(obj), (uint8_t)value); - } -#if TARGET_STM32H7 - /* Wait for RXP or end of Transfer */ - while (!LL_SPI_IsActiveFlag_RXP(SPI_INST(obj))); -#else /* TARGET_STM32H7 */ - /* Wait for RXNE flag before reading */ - while (!LL_SPI_IsActiveFlag_RXNE(SPI_INST(obj))); -#endif /* TARGET_STM32H7 */ + /* Transmit data */ + msp_wait_writable(obj); + msp_write_data(obj, value, bitshift); - /* Read received data */ - if (bitshift == 1) { - return LL_SPI_ReceiveData16(SPI_INST(obj)); -#ifdef HAS_32BIT_SPI_TRANSFERS - } else if (bitshift == 2) { - return LL_SPI_ReceiveData32(SPI_INST(obj)); -#endif - } else { - return LL_SPI_ReceiveData8(SPI_INST(obj)); - } + /* Receive data */ + msp_wait_readable(obj); + return msp_read_data(obj, bitshift); } int spi_master_block_write(spi_t *obj, const char *tx_buffer, int tx_length, @@ -861,18 +1106,11 @@ int spi_master_block_write(spi_t *obj, const char *tx_buffer, int tx_length, } } } else { - /* In case of 1 WIRE only, first handle TX, then Rx */ - if (tx_length != 0) { - if (HAL_OK != HAL_SPI_Transmit(handle, (uint8_t *)tx_buffer, tx_length, tx_length * TIMEOUT_1_BYTE)) { - /* report an error */ - total = 0; - } - } - if (rx_length != 0) { - if (HAL_OK != HAL_SPI_Receive(handle, (uint8_t *)rx_buffer, rx_length, rx_length * TIMEOUT_1_BYTE)) { - /* report an error */ - total = 0; - } + /* 1 wire case */ + int result = spi_master_one_wire_transfer(obj, tx_buffer, tx_length, rx_buffer, rx_length); + if (result != tx_length + rx_length) { + /* report an error */ + total = 0; } } @@ -1094,11 +1332,12 @@ void spi_master_transfer(spi_t *obj, const void *tx, size_t tx_length, void *rx, inline uint32_t spi_irq_handler_asynch(spi_t *obj) { int event = 0; + SPI_HandleTypeDef *handle = &(SPI_S(obj)->handle); // call the CubeF4 handler, this will update the handle - HAL_SPI_IRQHandler(&obj->spi.handle); + HAL_SPI_IRQHandler(handle); - if (obj->spi.handle.State == HAL_SPI_STATE_READY) { + if (handle->State == HAL_SPI_STATE_READY) { // When HAL SPI is back to READY state, check if there was an error int error = obj->spi.handle.ErrorCode; if (error != HAL_SPI_ERROR_NONE) { @@ -1116,9 +1355,19 @@ inline uint32_t spi_irq_handler_asynch(spi_t *obj) // disable the interrupt NVIC_DisableIRQ(obj->spi.spiIRQ); NVIC_ClearPendingIRQ(obj->spi.spiIRQ); +#ifndef TARGET_STM32H7 + if (handle->Init.Direction == SPI_DIRECTION_1LINE && obj->rx_buff.buffer != NULL) { + /** + * In case of 3-wire SPI data receiving we usually get dummy reads. + * So we need to cleanup FIFO/input register before next transmission. + * Probably it's better to set SPI_EVENT_RX_OVERFLOW event flag, + * but let's left it as is for backward compatibility. + */ + spi_flush_rx(obj); + } +#endif } - return (event & (obj->spi.event | SPI_EVENT_INTERNAL_TRANSFER_COMPLETE)); } @@ -1149,10 +1398,15 @@ void spi_abort_asynch(spi_t *obj) NVIC_DisableIRQ(irq_n); // clean-up - __HAL_SPI_DISABLE(handle); + LL_SPI_Disable(SPI_INST(obj)); HAL_SPI_DeInit(handle); HAL_SPI_Init(handle); - __HAL_SPI_ENABLE(handle); + // cleanup input buffer + spi_flush_rx(obj); + // enable SPI back if it isn't 3-wire mode + if (handle->Init.Direction != SPI_DIRECTION_1LINE) { + LL_SPI_Enable(SPI_INST(obj)); + } } #endif //DEVICE_SPI_ASYNCH