Skip to content

spi transfer16 is not 16 bit #2514

Open
@drmcnelson

Description

@drmcnelson

Describe the bug

SPI transfer16() produces two 8 byte transfers rather than one 16 bit transfer.

To Reproduce
Here is a code that is used for testing SPI transfers. In this instance, the CS pin tells an ADC to convert its input to digital, then 730 nanoseconds later the data is ready for a 16 bit SPI transfer.

With an SPI clock of 50kHz, this should be able to run at close to 1MSPS.

   for (int n = 0; n < NKNTS; n++ ) {

   // For the ADC
DIGITALWRITE(CSPin,HIGH);
DELAYNANOSECONDS(ADC_WAIT_NANOSECONDS);
DIGITALWRITE(CSPin,LOW);

// For the oscilloscope
DIGITALWRITE(SPAREPIN,HIGH);  

    // 16 bit transfer
data[n] = SPI.transfer16(0xFFFF);

// For the oscilloscope       
DIGITALWRITE(SPAREPIN,LOW);

  }

`

And here is the oscilloscope trace. Notice that rather than one 16 bit transfer, we have two 8 bit transfers. And note that they are separated by 400 nsecs, but there is 1200 nsecs before and after.

TEK0001

Here is the implementation, for transfer16() it calls spi_transfer( .... length )

uint16_t SPIClass::transfer16(uint16_t data, bool skipReceive)
{
uint16_t tmp;
if (_spiSettings.bitOrder) {
tmp = ((data & 0xff00) >> 8) | ((data & 0xff) << 8);
data = tmp;
}
spi_transfer(&_spi, (uint8_t *)&data, (!skipReceive) ? (uint8_t *)&data : NULL, sizeof(uint16_t));
if (_spiSettings.bitOrder) {
tmp = ((data & 0xff00) >> 8) | ((data & 0xff) << 8);
data = tmp;
}
return data;
}

And, in utility/spi.c, we find that spi_transfer() simply loops over LL_SPI_TransmitData8( ).

spi_status_e spi_transfer(spi_t *obj, const uint8_t *tx_buffer, uint8_t *rx_buffer,
uint16_t len)
{
spi_status_e ret = SPI_OK;
uint32_t tickstart, size = len;
SPI_TypeDef *_SPI = obj->handle.Instance;
uint8_t *tx_buf = (uint8_t *)tx_buffer;
if (len == 0) {
ret = SPI_ERROR;
} else {
tickstart = HAL_GetTick();
#if defined(SPI_CR2_TSIZE)
/* Start transfer */
LL_SPI_SetTransferSize(_SPI, size);
LL_SPI_Enable(_SPI);
LL_SPI_StartMasterTransfer(_SPI);
#endif
while (size--) {
#if defined(SPI_SR_TXP)
while (!LL_SPI_IsActiveFlag_TXP(_SPI));
#else
while (!LL_SPI_IsActiveFlag_TXE(_SPI));
#endif
LL_SPI_TransmitData8(_SPI, tx_buf ? *tx_buf++ : 0XFF);
#if defined(SPI_SR_RXP)
while (!LL_SPI_IsActiveFlag_RXP(_SPI));
#else
while (!LL_SPI_IsActiveFlag_RXNE(_SPI));
#endif
if (rx_buffer) {
*rx_buffer++ = LL_SPI_ReceiveData8(_SPI);
} else {
LL_SPI_ReceiveData8(_SPI);
}
if ((SPI_TRANSFER_TIMEOUT != HAL_MAX_DELAY) &&
(HAL_GetTick() - tickstart >= SPI_TRANSFER_TIMEOUT)) {
ret = SPI_TIMEOUT;
break;
}
}
#if defined(SPI_IFCR_EOTC)
// Add a delay before disabling SPI otherwise last-bit/last-clock may be truncated
// See https://github.com/stm32duino/Arduino_Core_STM32/issues/1294
// Computed delay is half SPI clock
delayMicroseconds(obj->disable_delay);
/* Close transfer */
/* Clear flags */
LL_SPI_ClearFlag_EOT(_SPI);
LL_SPI_ClearFlag_TXTF(_SPI);
/* Disable SPI peripheral */
LL_SPI_Disable(_SPI);
#else
/* Wait for end of transfer */
while (LL_SPI_IsActiveFlag_BSY(_SPI));
#endif
}
return ret;
}
#ifdef __cplusplus
}
#endif

You already have a LL_SPI_TransmitData16( ), under hardware/stm32/2.8.1/system/Drivers/

What is missing in this implementation is a spi_transfer16() and to change transfer16() to call it.

Thank you

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions