mirror of https://github.com/ARMmbed/mbed-os.git
Merge pull request #14981 from vznncv/fix-stm32-spi-3wire
Fix STM32 SPI 3-wire (synchronous API)pull/15071/head
commit
d87183a52b
|
@ -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_<X> 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 */
|
||||
#endif
|
||||
|
||||
/* 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);
|
||||
}
|
||||
msp_wait_writable(obj);
|
||||
msp_write_data(obj, value, bitshift);
|
||||
|
||||
#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 */
|
||||
|
||||
/* 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
|
||||
|
|
Loading…
Reference in New Issue