Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(spi): add transfer api with tx and rx buffer #2206

Merged
merged 3 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 23 additions & 9 deletions libraries/SPI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,18 @@ User have 2 possibilities about the management of the CS pin:
* the CS pin is managed directly by the user code before to transfer the data (like the Arduino SPI library)
* the user uses a hardware CS pin linked to the SPI peripheral

### New API functions
## New API functions

* `SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel)`: alternative class constructor
_Params_ SPI `mosi` pin
_Params_ SPI `miso` pin
_Params_ SPI `sclk` pin
_Params_ (optional) SPI `ssel` pin. This pin must be an hardware CS pin. If you configure this pin, the chip select will be managed by the SPI peripheral.
#### Alternative class constructor
* `SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel)`

_Param_ SPI `mosi` pin

* `SPI_HandleTypeDef *getHandle(void)`: Could be used to mix Arduino API and STM32Cube HAL API (ex: DMA). **Use at your own risk.**
_Param_ SPI `miso` pin

_Param_ SPI `sclk` pin

_Params_ (optional) SPI `ssel` pin. This pin must be an hardware CS pin. If you configure this pin, the chip select will be managed by the SPI peripheral.

##### Example

Expand All @@ -35,9 +37,15 @@ void setup() {
}
```

### Extended API
#### Transfer with Tx/Rx buffer

* `void transfer(const void *tx_buf, void *rx_buf, size_t count)` :Transfer several bytes. One constant buffer used to send and one to receive data.

* All `transfer()` API's have a new bool argument `skipReceive`. It allows to skip receive data after transmitting. Value can be `SPI_TRANSMITRECEIVE` or `SPI_TRANSMITONLY`. Default `SPI_TRANSMITRECEIVE`.
_Param_ `tx_buf`: constant array of Tx bytes that is filled by the user before starting the SPI transfer. If NULL, default dummy 0xFF bytes will be clocked out.

_Param_ `rx_buf`: array of Rx bytes that will be filled by the slave during the SPI transfer. If NULL, the received data will be discarded.

_Param_ `count`: number of bytes to send/receive.

#### Change default `SPI` instance pins
It is also possible to change the default pins used by the `SPI` instance using above API:
Expand All @@ -63,3 +71,9 @@ It is also possible to change the default pins used by the `SPI` instance using
SPI.setMOSI(PC2); // using pin number PYn
SPI.begin(2);
```

* `SPI_HandleTypeDef *getHandle(void)`: Could be used to mix Arduino API and STM32Cube HAL API (ex: DMA). **Use at your own risk.**

## Extended API

* All defaustatndard `transfer()` API's have a new bool argument `skipReceive`. It allows to skip receive data after transmitting. Value can be `SPI_TRANSMITRECEIVE` or `SPI_TRANSMITONLY`. Default `SPI_TRANSMITRECEIVE`.
28 changes: 21 additions & 7 deletions libraries/SPI/src/SPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ void SPIClass::setClockDivider(uint8_t divider)
*/
uint8_t SPIClass::transfer(uint8_t data, bool skipReceive)
{
spi_transfer(&_spi, &data, sizeof(uint8_t), SPI_TRANSFER_TIMEOUT, skipReceive);
spi_transfer(&_spi, &data, (!skipReceive) ? &data : NULL, sizeof(uint8_t));
return data;
}

Expand All @@ -184,8 +184,7 @@ uint16_t SPIClass::transfer16(uint16_t data, bool skipReceive)
tmp = ((data & 0xff00) >> 8) | ((data & 0xff) << 8);
data = tmp;
}
spi_transfer(&_spi, (uint8_t *)&data, sizeof(uint16_t),
SPI_TRANSFER_TIMEOUT, skipReceive);
spi_transfer(&_spi, (uint8_t *)&data, (!skipReceive) ? (uint8_t *)&data : NULL, sizeof(uint16_t));

if (_spiSettings.bitOrder) {
tmp = ((data & 0xff00) >> 8) | ((data & 0xff) << 8);
Expand All @@ -207,12 +206,27 @@ uint16_t SPIClass::transfer16(uint16_t data, bool skipReceive)
*/
void SPIClass::transfer(void *buf, size_t count, bool skipReceive)
{
if ((count != 0) && (buf != NULL)) {
spi_transfer(&_spi, ((uint8_t *)buf), count,
SPI_TRANSFER_TIMEOUT, skipReceive);
}
spi_transfer(&_spi, (uint8_t *)buf, (!skipReceive) ? (uint8_t *)buf : NULL, count);

}

/**
* @brief Transfer several bytes. One constant buffer used to send and
* one to receive data.
* begin() or beginTransaction() must be called at least once before.
* @param tx_buf: array of Tx bytes that is filled by the user before starting
* the SPI transfer. If NULL, default dummy 0xFF bytes will be
* clocked out.
* @param rx_buf: array of Rx bytes that will be filled by the slave during
* the SPI transfer. If NULL, the received data will be discarded.
* @param count: number of bytes to send/receive.
*/
void SPIClass::transfer(const void *tx_buf, void *rx_buf, size_t count)
{
spi_transfer(&_spi, ((const uint8_t *)tx_buf), ((uint8_t *)rx_buf), count);
}


/**
* @brief Not implemented.
*/
Expand Down
20 changes: 10 additions & 10 deletions libraries/SPI/src/SPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,6 @@ extern "C" {
#define SPI_TRANSMITRECEIVE false
#define SPI_TRANSMITONLY true

// Defines a default timeout delay in milliseconds for the SPI transfer
#ifndef SPI_TRANSFER_TIMEOUT
#define SPI_TRANSFER_TIMEOUT 1000
#endif

class SPISettings {
public:
constexpr SPISettings(uint32_t clock, BitOrder bitOrder, uint8_t dataMode)
Expand Down Expand Up @@ -127,21 +122,26 @@ class SPIClass {
_spi.pin_ssel = (ssel);
};

virtual void begin(void);
void begin(void);
void end(void);

/* This function should be used to configure the SPI instance in case you
* don't use default parameters.
*/
void beginTransaction(SPISettings settings);
virtual void endTransaction(void);
void endTransaction(void);

/* Transfer functions: must be called after initialization of the SPI
* instance with begin() or beginTransaction().
*/
virtual uint8_t transfer(uint8_t data, bool skipReceive = SPI_TRANSMITRECEIVE);
virtual uint16_t transfer16(uint16_t data, bool skipReceive = SPI_TRANSMITRECEIVE);
virtual void transfer(void *buf, size_t count, bool skipReceive = SPI_TRANSMITRECEIVE);
uint8_t transfer(uint8_t data, bool skipReceive = SPI_TRANSMITRECEIVE);
uint16_t transfer16(uint16_t data, bool skipReceive = SPI_TRANSMITRECEIVE);
void transfer(void *buf, size_t count, bool skipReceive = SPI_TRANSMITRECEIVE);

/* Expand SPI API
* https://github.com/arduino/ArduinoCore-API/discussions/189
*/
void transfer(const void *tx_buf, void *rx_buf, size_t count);

/* These methods are deprecated and kept for compatibility.
* Use SPISettings with SPI.beginTransaction() to configure SPI parameters.
Expand Down
80 changes: 41 additions & 39 deletions libraries/SPI/src/utility/spi_com.c
Original file line number Diff line number Diff line change
Expand Up @@ -500,71 +500,73 @@ void spi_deinit(spi_t *obj)
* @brief This function is implemented by user to send/receive data over
* SPI interface
* @param obj : pointer to spi_t structure
* @param buffer : tx data to send before reception
* @param tx_buffer : tx data to send before reception
* @param rx_buffer : rx data to receive if not numm
* @param len : length in byte of the data to send and receive
* @param Timeout: Timeout duration in tick
* @param skipReceive: skip receiving data after transmit or not
* @retval status of the send operation (0) in case of error
*/
spi_status_e spi_transfer(spi_t *obj, uint8_t *buffer, uint16_t len,
uint32_t Timeout, bool skipReceive)
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_buffer = buffer;
uint8_t *tx_buf = (uint8_t *)tx_buffer;

if ((len == 0) || (Timeout == 0U)) {
return Timeout > 0U ? SPI_ERROR : SPI_TIMEOUT;
}
tickstart = HAL_GetTick();
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);
/* Start transfer */
LL_SPI_SetTransferSize(_SPI, size);
LL_SPI_Enable(_SPI);
LL_SPI_StartMasterTransfer(_SPI);
#endif

while (size--) {
while (size--) {
#if defined(SPI_SR_TXP)
while (!LL_SPI_IsActiveFlag_TXP(_SPI));
while (!LL_SPI_IsActiveFlag_TXP(_SPI));
#else
while (!LL_SPI_IsActiveFlag_TXE(_SPI));
while (!LL_SPI_IsActiveFlag_TXE(_SPI));
#endif
LL_SPI_TransmitData8(_SPI, *tx_buffer++);
LL_SPI_TransmitData8(_SPI, tx_buf ? *tx_buf++ : 0XFF);

if (!skipReceive) {
#if defined(SPI_SR_RXP)
while (!LL_SPI_IsActiveFlag_RXP(_SPI));
#else
while (!LL_SPI_IsActiveFlag_RXNE(_SPI));
#endif
*buffer++ = LL_SPI_ReceiveData8(_SPI);
}
if ((Timeout != HAL_MAX_DELAY) && (HAL_GetTick() - tickstart >= Timeout)) {
ret = SPI_TIMEOUT;
break;
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);
// 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));
/* Wait for end of transfer */
while (LL_SPI_IsActiveFlag_BSY(_SPI));
#endif

}
return ret;
}

Expand Down
10 changes: 8 additions & 2 deletions libraries/SPI/src/utility/spi_com.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,13 @@ typedef struct spi_s spi_t;
#define SPI_SPEED_CLOCK_DIV128_MHZ ((uint32_t)128)
#define SPI_SPEED_CLOCK_DIV256_MHZ ((uint32_t)256)

// Defines a default timeout delay in milliseconds for the SPI transfer
#ifndef SPI_TRANSFER_TIMEOUT
#define SPI_TRANSFER_TIMEOUT 1000
#elif SPI_TRANSFER_TIMEOUT <= 0
#error "SPI_TRANSFER_TIMEOUT cannot be less or equal to 0!"
#endif

///@brief specifies the SPI mode to use
//Mode Clock Polarity (CPOL) Clock Phase (CPHA)
//SPI_MODE0 0 0
Expand All @@ -103,8 +110,7 @@ typedef enum {
/* Exported functions ------------------------------------------------------- */
void spi_init(spi_t *obj, uint32_t speed, SPIMode mode, uint8_t msb);
void spi_deinit(spi_t *obj);
spi_status_e spi_transfer(spi_t *obj, uint8_t *buffer, uint16_t len,
uint32_t Timeout, bool skipReceive);
spi_status_e spi_transfer(spi_t *obj, const uint8_t *tx_buffer, uint8_t *rx_buffer, uint16_t len);
uint32_t spi_getClkFreq(spi_t *obj);

#ifdef __cplusplus
Expand Down