diff --git a/libraries/SPI/README.md b/libraries/SPI/README.md new file mode 100644 index 0000000000..36f3bd5738 --- /dev/null +++ b/libraries/SPI/README.md @@ -0,0 +1,70 @@ +## SPI + +STM32 SPI library has been modified with the possibility to manage several CS pins without to stop the SPI interface. +_We do not describe here the [SPI Arduino API](https://www.arduino.cc/en/Reference/SPI) but the functionalities added._ + +We give to the user 3 possiblities about the management of the CS pin: +1. the CS pin is managed directly by the user code before to transfer the data (like the Arduino SPI library) +2. the user gives the CS pin number to the library API and the library manages itself the CS pin (see example below) +3. the user uses a hardware CS pin linked to the SPI peripheral + +### 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. Do not use API functions with CS pin in parameter. + +* **`void SPIClass::begin(uint8_t _pin)`**: initialize the SPI interface and add a CS pin +_Params_ spi CS pin to be managed by the SPI library + +* **`void beginTransaction(uint8_t pin, SPISettings settings)`**: allows to configure the SPI with other parameter. These new parameter are saved this an associated CS pin. +_Params_ SPI CS pin to be managed by the SPI library +_Params_ SPI settings + +* **`void endTransaction(uint8_t pin)`**: removes a CS pin and the SPI settings associated +_Params_ SPI CS pin managed by the SPI library + +**_Note 1_** The following functions must be called after initialization of the SPI instance with `begin()` or `beginTransaction()`. +If you have several device to manage, you can call `beginTransaction()` several time with different CS pin in parameter. +Then you can call the following functions with different CS pin without call again `beginTransaction()` (until you call `end()` or `endTransaction()`). + +**_Note 2_** If the mode is set to `SPI_CONTINUE`, the CS pin is kept enabled. Be careful in case you use several CS pin. + +* **`byte transfer(uint8_t pin, uint8_t _data, SPITransferMode _mode = SPI_LAST)`**: write/read one byte +_Params_ SPI CS pin managed by the SPI library +_Params_ data to write +_Params_ (optional) if `SPI_LAST` CS pin is reset, `SPI_CONTINUE` the CS pin is kept enabled. +_Return_ byte received + +* **`uint16_t transfer16(uint8_t pin, uint16_t _data, SPITransferMode _mode = SPI_LAST)`**: write/read half-word +_Params_ SPI CS pin managed by the SPI library +_Params_ 16bits data to write +_Params_ (optional) if `SPI_LAST` CS pin is reset, `SPI_CONTINUE` the CS pin is kept enabled. +_Return_ 16bits data received + +* **`void transfer(uint8_t pin, void *_buf, size_t _count, SPITransferMode _mode = SPI_LAST)`**: write/read several bytes. Only one buffer used to write and read the data +_Params_ SPI CS pin managed by the SPI library +_Params_ pointer to data to write. The data will be replaced by the data read. +_Params_ number of data to write/read. +_Params_ (optional) if `SPI_LAST` CS pin is reset, `SPI_CONTINUE` the CS pin is kept enabled. + +* **`void transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode = SPI_LAST)`**: write/read several bytes. One buffer for the output data and one for the input data +_Params_ SPI CS pin managed by the SPI library +_Params_ pointer to data to write. +_Params_ pointer where to store the data read. +_Params_ number of data to write/read. +_Params_ (optional) if `SPI_LAST` CS pin is reset, `SPI_CONTINUE` the CS pin is kept enabled. + +### Example + +This is an example of the use of the CS pin management: + +```C++ +SPI.begin(2); //Enables the SPI instance with default settings and attachs the CS pin +SPI.beginTransaction(1, settings); //Attachs another CS pin and configure the SPI instance with other settings +SPI.transfer(1, 0x52); //Transfers data to the first device +SPI.transfer(2, 0xA4); //Transfers data to the second device. The SPI instance is configured with the right settings +SPI.end() //SPI instance is disabled +``` diff --git a/libraries/SPI/src/SPI.cpp b/libraries/SPI/src/SPI.cpp index d2aa430aa1..f77cd23c67 100644 --- a/libraries/SPI/src/SPI.cpp +++ b/libraries/SPI/src/SPI.cpp @@ -11,14 +11,12 @@ #include "SPI.h" -/* The following contructors are available: -- SPIClass SPI -- SPIClass SPI(mosi,miso,sclk) -- SPIClass SPI(mosi,miso,sclk,ss) -*/ SPIClass SPI; -SPIClass::SPIClass() : _CSpin(-1) +/** + * @brief Default constructor. Uses pin configuration of variant.h. + */ +SPIClass::SPIClass() : _CSPinConfig(NO_CONFIG) { _spi.pin_miso = digitalPinToPinName(MISO); _spi.pin_mosi = digitalPinToPinName(MOSI); @@ -26,29 +24,39 @@ SPIClass::SPIClass() : _CSpin(-1) _spi.pin_ssel = NC; } -/* By default hardware SS pin is not used. To use hardware SS pin you should set -ssel pin. Enable this pin disable software CS. See microcontroller documentation -for the list of available SS pins. */ -SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel) : _CSpin(-1) +/** + * @brief Constructor to create another SPI instance attached to another SPI + * peripheral different of the default SPI. All pins must be attached to + * the same SPI peripheral. See datasheet of the microcontroller. + * @param mosi: SPI mosi pin. Accepted format: number or Arduino format (Dx) + * or ST format (Pxy). + * @param miso: SPI miso pin. Accepted format: number or Arduino format (Dx) + * or ST format (Pxy). + * @param sclk: SPI clock pin. Accepted format: number or Arduino format (Dx) + * or ST format (Pxy). + * @param ssel: SPI ssel pin (optional). Accepted format: number or + * Arduino format (Dx) or ST format (Pxy). By default is set to NC. + * This pin must correspond to a hardware CS pin which can be managed + * by the SPI peripheral itself. See the datasheet of the microcontroller + * or look at PinMap_SPI_SSEL[] inside the file PeripheralPins.c + * corresponding to the board. If you configure this pin you can't use + * another CS pin and don't pass a CS pin as parameter to any functions + * of the class. + */ +SPIClass::SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel) : _CSPinConfig(NO_CONFIG) { _spi.pin_miso = digitalPinToPinName(miso); _spi.pin_mosi = digitalPinToPinName(mosi); _spi.pin_sclk = digitalPinToPinName(sclk); - - if(ssel != 0xFF) { - _spi.pin_ssel = digitalPinToPinName(ssel); - } else { - _spi.pin_ssel = NC; - } -} - -//begin using the default chip select -void SPIClass::begin() -{ - begin(CS_PIN_CONTROLLED_BY_USER); + _spi.pin_ssel = digitalPinToPinName(ssel); } -//Begin with a chip select defined +/** + * @brief Initialize the SPI instance. + * @param _pin: chip select pin (optional). If this parameter is filled, + * it gives the management of the CS pin to the SPI class. In this case + * do not manage the CS pin outside of the SPI class. + */ void SPIClass::begin(uint8_t _pin) { uint8_t idx; @@ -69,7 +77,7 @@ void SPIClass::begin(uint8_t _pin) spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; #if __has_include("WiFi.h") // Wait wifi shield initialization. // Should be better to do in SpiDrv::begin() of WiFi library but it seems @@ -79,12 +87,16 @@ void SPIClass::begin(uint8_t _pin) } -void SPIClass::usingInterrupt(uint8_t interruptNumber) -{ - UNUSED(interruptNumber); - //Not implemented -} - +/** + * @brief This function should be used to configure the SPI instance in case you + * don't use the default parameters set by the begin() function. + * @param _pin: CS pin (optional). This pin will be attached with the settings. + * @param settings: SPI settings(clock speed, bit order, data mode). + * @Note For each SPI instance you are able to manage until NB_SPI_SETTINGS + * devices attached to the same SPI peripheral. You need to indicate the + * CS pin used to the transfer() function and the SPI instance will be + * configured with the right settings. + */ void SPIClass::beginTransaction(uint8_t _pin, SPISettings settings) { uint8_t idx; @@ -114,25 +126,38 @@ void SPIClass::beginTransaction(uint8_t _pin, SPISettings settings) spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; } +/** + * @brief Remove the CS pin and the settings associated to the SPI instance. + * @param _pin: CS pin (optional) + */ void SPIClass::endTransaction(uint8_t _pin) { if(_pin > NUM_DIGITAL_PINS) return; RemovePin(_pin); - _CSpin = -1; + _CSPinConfig = NO_CONFIG; } +/** + * @brief Deinitialize the SPI instance and stop it. + */ void SPIClass::end() { spi_deinit(&_spi); RemoveAllPin(); - _CSpin = -1; + _CSPinConfig = NO_CONFIG; } +/** + * @brief Deprecated function. + * Configure the bit order: MSB first or LSB first. + * @param _pin: CS pin associated to a configuration (optional). + * @param _bitOrder: MSBFIRST or LSBFIRST + */ void SPIClass::setBitOrder(uint8_t _pin, BitOrder _bitOrder) { if(_pin > NUM_DIGITAL_PINS) @@ -156,6 +181,18 @@ void SPIClass::setBitOrder(uint8_t _pin, BitOrder _bitOrder) spiSettings[idx].msb); } +/** + * @brief Deprecated function. + * Configure the data mode (clock polarity and clock phase) + * @param _pin: CS pin associated to a configuration (optional). + * @param _mode: SPI_MODE0, SPI_MODE1, SPI_MODE2 or SPI_MODE3 + * @note + * Mode Clock Polarity (CPOL) Clock Phase (CPHA) + * SPI_MODE0 0 0 + * SPI_MODE1 0 1 + * SPI_MODE2 1 0 + * SPI_MODE3 1 1 + */ void SPIClass::setDataMode(uint8_t _pin, uint8_t _mode) { if(_pin > NUM_DIGITAL_PINS) @@ -181,10 +218,19 @@ void SPIClass::setDataMode(uint8_t _pin, uint8_t _mode) spiSettings[idx].msb); } -/* - * This function should not be used in new projects. - * Use SPISettings with SPI.beginTransaction() to configure SPI parameters. - */ +/** + * @brief Deprecated function. + * Configure the clock speed: 125kHz to 8MHz. + * @param _pin: CS pin associated to a configuration (optional). + * @param _divider: can be one of the following parameters: + * SPI_CLOCK_DIV2 (8MHz) + * SPI_CLOCK_DIV4 (4MHz) + * SPI_CLOCK_DIV8 (2MHz) + * SPI_CLOCK_DIV16 (1MHz) + * SPI_CLOCK_DIV32 (500kHz) + * SPI_CLOCK_DIV64 (250kHz) + * SPI_CLOCK_DIV128 (125kHz) + */ void SPIClass::setClockDivider(uint8_t _pin, uint8_t _divider) { if(_pin > NUM_DIGITAL_PINS) @@ -218,13 +264,19 @@ void SPIClass::setClockDivider(uint8_t _pin, uint8_t _divider) spiSettings[idx].msb); } - -/* Transfer a message on the selected SPI. The _pin is the CS. - * The transfer function can reconfigure the SPI instance if the CS pin is - * different from the previous one. - * If the _mode is set to SPI_CONTINUE, keep the spi instance alive. That means - * the CS pin is not reset. Be careful in case you use several CS pin. - */ +/** + * @brief Transfer one byte on the SPI bus. + * begin() or beginTransaction() must be called at least once before. + * @param _pin: CS pin to select a device (optional). If the previous transfer + * used another CS pin then the SPI instance will be reconfigured. + * @param data: byte to send. + * @param _mode: (optional) can be SPI_CONTINUE in case of multiple successive + * send or SPI_LAST to indicate the end of send. + * If the _mode is set to SPI_CONTINUE, keep the SPI instance alive. + * That means the CS pin is not reset. Be careful in case you use + * several CS pin. + * @return byte received from the slave. + */ byte SPIClass::transfer(uint8_t _pin, uint8_t data, SPITransferMode _mode) { uint8_t rx_buffer = 0; @@ -232,7 +284,7 @@ byte SPIClass::transfer(uint8_t _pin, uint8_t data, SPITransferMode _mode) if (_pin > NUM_DIGITAL_PINS) return rx_buffer; - if(_pin != _CSpin) { + if(_pin != _CSPinConfig) { uint8_t idx = pinIdx(_pin, GET_IDX); if(idx >= NB_SPI_SETTINGS) { return rx_buffer; @@ -240,7 +292,7 @@ byte SPIClass::transfer(uint8_t _pin, uint8_t data, SPITransferMode _mode) spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; } if((_pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC)) @@ -254,6 +306,19 @@ byte SPIClass::transfer(uint8_t _pin, uint8_t data, SPITransferMode _mode) return rx_buffer; } +/** + * @brief Transfer two bytes on the SPI bus in 16 bits format. + * begin() or beginTransaction() must be called at least once before. + * @param _pin: CS pin to select a device (optional). If the previous transfer + * used another CS pin then the SPI instance will be reconfigured. + * @param data: bytes to send. + * @param _mode: (optional) can be SPI_CONTINUE in case of multiple successive + * send or SPI_LAST to indicate the end of send. + * If the _mode is set to SPI_CONTINUE, keep the SPI instance alive. + * That means the CS pin is not reset. Be careful in case you use + * several CS pin. + * @return bytes received from the slave in 16 bits format. + */ uint16_t SPIClass::transfer16(uint8_t _pin, uint16_t data, SPITransferMode _mode) { uint16_t rx_buffer = 0; @@ -267,11 +332,11 @@ uint16_t SPIClass::transfer16(uint8_t _pin, uint16_t data, SPITransferMode _mode return rx_buffer; } - if(_pin != _CSpin) { + if(_pin != _CSPinConfig) { spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; } if (spiSettings[idx].msb) { @@ -295,12 +360,26 @@ uint16_t SPIClass::transfer16(uint8_t _pin, uint16_t data, SPITransferMode _mode return rx_buffer; } +/** + * @brief Transfer several bytes. Only one buffer used to send and receive data. + * begin() or beginTransaction() must be called at least once before. + * @param _pin: CS pin to select a device (optional). If the previous transfer + * used another CS pin then the SPI instance will be reconfigured. + * @param _buf: pointer to the bytes to send. The bytes received are copy in + * this buffer. + * @param _count: number of bytes to send/receive. + * @param _mode: (optional) can be SPI_CONTINUE in case of multiple successive + * send or SPI_LAST to indicate the end of send. + * If the _mode is set to SPI_CONTINUE, keep the SPI instance alive. + * That means the CS pin is not reset. Be careful in case you use + * several CS pin. + */ void SPIClass::transfer(uint8_t _pin, void *_buf, size_t _count, SPITransferMode _mode) { if ((_count == 0) || (_buf == NULL) || (_pin > NUM_DIGITAL_PINS)) return; - if(_pin != _CSpin) { + if(_pin != _CSPinConfig) { uint8_t idx = pinIdx(_pin, GET_IDX); if(idx >= NB_SPI_SETTINGS) { return; @@ -308,7 +387,7 @@ void SPIClass::transfer(uint8_t _pin, void *_buf, size_t _count, SPITransferMode spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; } if((_pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC)) @@ -320,12 +399,27 @@ void SPIClass::transfer(uint8_t _pin, void *_buf, size_t _count, SPITransferMode digitalWrite(_pin, HIGH); } +/** + * @brief Transfer several bytes. One buffer contains the data to send and + * another one will contains the data received. begin() or + * beginTransaction() must be called at least once before. + * @param _pin: CS pin to select a device (optional). If the previous transfer + * used another CS pin then the SPI instance will be reconfigured. + * @param _bufout: pointer to the bytes to send. + * @param _bufin: pointer to the bytes received. + * @param _count: number of bytes to send/receive. + * @param _mode: (optional) can be SPI_CONTINUE in case of multiple successive + * send or SPI_LAST to indicate the end of send. + * If the _mode is set to SPI_CONTINUE, keep the SPI instance alive. + * That means the CS pin is not reset. Be careful in case you use + * several CS pin. + */ void SPIClass::transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, SPITransferMode _mode) { if ((_count == 0) || (_bufout == NULL) || (_bufin == NULL) || (_pin > NUM_DIGITAL_PINS)) return; - if(_pin != _CSpin) { + if(_pin != _CSPinConfig) { uint8_t idx = pinIdx(_pin, GET_IDX); if(idx >= NB_SPI_SETTINGS) { return; @@ -333,7 +427,7 @@ void SPIClass::transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, S spi_init(&_spi, spiSettings[idx].clk, spiSettings[idx].dMode, spiSettings[idx].msb); - _CSpin = _pin; + _CSPinConfig = _pin; } if((_pin != CS_PIN_CONTROLLED_BY_USER) && (_spi.pin_ssel == NC)) @@ -345,10 +439,24 @@ void SPIClass::transfer(byte _pin, void *_bufout, void *_bufin, size_t _count, S digitalWrite(_pin, HIGH); } +/** + * @brief Not implemented. + */ +void SPIClass::usingInterrupt(uint8_t interruptNumber) +{ + UNUSED(interruptNumber); +} + +/** + * @brief Not implemented. + */ void SPIClass::attachInterrupt(void) { // Should be enableInterrupt() } +/** + * @brief Not implemented. + */ void SPIClass::detachInterrupt(void) { // Should be disableInterrupt() } diff --git a/libraries/SPI/src/SPI.h b/libraries/SPI/src/SPI.h index ab57273eb1..e3a73f8163 100644 --- a/libraries/SPI/src/SPI.h +++ b/libraries/SPI/src/SPI.h @@ -34,24 +34,29 @@ #define SPI_CLOCK_DIV64 64 #define SPI_CLOCK_DIV128 128 +// SPI mode parameters for SPISettings #define SPI_MODE0 0x00 #define SPI_MODE1 0x01 #define SPI_MODE2 0x02 #define SPI_MODE3 0x03 +// Transfer mode enum SPITransferMode { - SPI_CONTINUE, - SPI_LAST + SPI_CONTINUE, /* Transfer not finished: CS pin kept active */ + SPI_LAST /* Transfer ended: CS pin released */ }; -// Indicates the user controls himself the CS pin +// Indicates the user controls himself the CS pin outside of the spi class #define CS_PIN_CONTROLLED_BY_USER NUM_DIGITAL_PINS +// Indicates there is no configuration selected +#define NO_CONFIG ((int16_t)(-1)) + // Defines a default timeout delay in milliseconds for the SPI transfer #define SPI_TRANSFER_TIMEOUT 1000 /* - * Defines the number of settings saved per SPI instance. Must be < 254. + * Defines the number of settings saved per SPI instance. Must be in range 1 to 254. * Can be redefined in variant.h */ #ifndef NB_SPI_SETTINGS @@ -89,9 +94,9 @@ class SPISettings { dMode = SPI_MODE_0; } private: - int16_t pinCS; - uint32_t clk; //specifies the spi bus maximum clock speed - BitOrder bOrder; //bit order (MSB/LSB first) + int16_t pinCS; //CS pin associated to the configuration + uint32_t clk; //specifies the spi bus maximum clock speed + BitOrder bOrder; //bit order (MSBFirst or LSBFirst) spi_mode_e dMode; //one of the data mode //Mode Clock Polarity (CPOL) Clock Phase (CPHA) //SPI_MODE0 0 0 @@ -105,7 +110,27 @@ class SPISettings { class SPIClass { public: SPIClass(); - SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel = 0xFF); + SPIClass(uint8_t mosi, uint8_t miso, uint8_t sclk, uint8_t ssel = (uint8_t)NC); + + void begin(uint8_t _pin = CS_PIN_CONTROLLED_BY_USER); + void end(void); + + /* This function should be used to configure the SPI instance in case you + * don't use default parameters. + * You can attach another CS pin to the SPI instance and each CS pin can be + * attach with specific SPI settings. + */ + void beginTransaction(uint8_t pin, SPISettings settings); + void beginTransaction(SPISettings settings) + { + beginTransaction(CS_PIN_CONTROLLED_BY_USER, settings); + } + + void endTransaction(uint8_t pin); + void endTransaction(void) + { + endTransaction(CS_PIN_CONTROLLED_BY_USER); + } /* Transfer functions: must be called after initialization of the SPI * instance with begin() or beginTransaction(). @@ -137,63 +162,35 @@ class SPIClass { transfer(CS_PIN_CONTROLLED_BY_USER, _bufout, _bufin, _count, _mode); } - // Transaction Functions - void usingInterrupt(uint8_t interruptNumber); - - /* This function should be used to configure the SPI instance in case you - * don't use default parameters. - * You can attach another CS pin to the SPI instance and each CS pin can be - * attach with specific SPI settings. - */ - void beginTransaction(uint8_t pin, SPISettings settings); - void beginTransaction(SPISettings settings) - { - beginTransaction(CS_PIN_CONTROLLED_BY_USER, settings); - } - - void endTransaction(uint8_t pin); - void endTransaction(void) - { - endTransaction(CS_PIN_CONTROLLED_BY_USER); - } - - // SPI Configuration methods - void attachInterrupt(void); - void detachInterrupt(void); - - void begin(void); - void end(void); - - // Attach CS pin to SPI instance - void begin(uint8_t _pin); - - /* These methods sets a parameter on a single pin. - * These methods must be called after initialization of the SPI - * instance with begin() or beginTransaction(). - * @NOTE - * These functions should not be used in new projects. + /* These methods are deprecated and kept for compatibility. * Use SPISettings with SPI.beginTransaction() to configure SPI parameters. */ void setBitOrder(uint8_t _pin, BitOrder); + void setBitOrder(BitOrder _order) {setBitOrder(CS_PIN_CONTROLLED_BY_USER, _order);} + void setDataMode(uint8_t _pin, uint8_t); + void setDataMode(uint8_t _mode) {setDataMode(CS_PIN_CONTROLLED_BY_USER, _mode);} + void setClockDivider(uint8_t _pin, uint8_t); + void setClockDivider(uint8_t _div) {setClockDivider(CS_PIN_CONTROLLED_BY_USER, _div);} - // These methods sets the same parameters for the default CS pin. - void setBitOrder(BitOrder _order) { - setBitOrder(CS_PIN_CONTROLLED_BY_USER, _order); - } - void setDataMode(uint8_t _mode) { - setDataMode(CS_PIN_CONTROLLED_BY_USER, _mode); - } - void setClockDivider(uint8_t _div) { - setClockDivider(CS_PIN_CONTROLLED_BY_USER, _div); - } + // Not implemented functions. Kept for backward compatibility. + void usingInterrupt(uint8_t interruptNumber); + void attachInterrupt(void); + void detachInterrupt(void); private: + /* Contains various spiSettings for the same spi instance. Each spi spiSettings + is associated to a CS pin. */ SPISettings spiSettings[NB_SPI_SETTINGS]; - int16_t _CSpin; + + // Use to know which configuration is selected. + int16_t _CSPinConfig; + + // spi instance spi_t _spi; + typedef enum{ GET_IDX = 0, ADD_NEW_PIN = 1