Jelajahi Sumber

Merge branch 'feature/spi_little_endien' into 'master'

feature(spi): provide macro to write multi-byte data straightly

See merge request idf/esp-idf!2634
Angus Gratton 7 tahun lalu
induk
melakukan
52f4ff6220

+ 45 - 17
components/driver/include/driver/spi_common.h

@@ -31,6 +31,34 @@ extern "C"
 //Maximum amount of bytes that can be put in one DMA descriptor
 #define SPI_MAX_DMA_LEN (4096-4)
 
+/**
+ * Transform unsigned integer of length <= 32 bits to the format which can be
+ * sent by the SPI driver directly.
+ *
+ * E.g. to send 9 bits of data, you can:
+ *
+ *      uint16_t data = SPI_SWAP_DATA_TX(0x145, 9);
+ *
+ * Then points tx_buffer to ``&data``.
+ *
+ * @param data Data to be sent, can be uint8_t, uint16_t or uint32_t. @param
+ *  len Length of data to be sent, since the SPI peripheral sends from the MSB,
+ *  this helps to shift the data to the MSB.
+ */
+#define SPI_SWAP_DATA_TX(data, len) __builtin_bswap32((uint32_t)data<<(32-len))
+
+/**
+ * Transform received data of length <= 32 bits to the format of an unsigned integer.
+ *
+ * E.g. to transform the data of 15 bits placed in a 4-byte array to integer:
+ *
+ *      uint16_t data = SPI_SWAP_DATA_RX(*(uint32_t*)t->rx_data, 15);
+ *
+ * @param data Data to be rearranged, can be uint8_t, uint16_t or uint32_t.
+ * @param len Length of data received, since the SPI peripheral writes from
+ *      the MSB, this helps to shift the data to the LSB.
+ */
+#define SPI_SWAP_DATA_RX(data, len) (__builtin_bswap32(data)>>(32-len))
 
 /**
  * @brief Enum with the three SPI peripherals that are software-accessible in it
@@ -45,7 +73,7 @@ typedef enum {
  * @brief This is a configuration structure for a SPI bus.
  *
  * You can use this structure to specify the GPIO pins of the bus. Normally, the driver will use the
- * GPIO matrix to route the signals. An exception is made when all signals either can be routed through 
+ * GPIO matrix to route the signals. An exception is made when all signals either can be routed through
  * the IO_MUX or are -1. In that case, the IO_MUX is used, allowing for >40MHz speeds.
  *
  * @note Be advised that the slave driver does not use the quadwp/quadhd lines and fields in spi_bus_config_t refering to these lines will be ignored and can thus safely be left uninitialized.
@@ -81,20 +109,20 @@ bool spicommon_periph_free(spi_host_device_t host);
 
 /**
  * @brief Try to claim a SPI DMA channel
- * 
+ *
  *  Call this if your driver wants to use SPI with a DMA channnel.
- * 
+ *
  * @param dma_chan channel to claim
- * 
+ *
  * @return True if success; false otherwise.
  */
 bool spicommon_dma_chan_claim(int dma_chan);
 
 /**
  * @brief Return the SPI DMA channel so other driver can claim it, or just to power down DMA.
- * 
+ *
  * @param dma_chan channel to return
- * 
+ *
  * @return True if success; false otherwise.
  */
 bool spicommon_dma_chan_free(int dma_chan);
@@ -113,7 +141,7 @@ bool spicommon_dma_chan_free(int dma_chan);
  * @brief Connect a SPI peripheral to GPIO pins
  *
  * This routine is used to connect a SPI peripheral to the IO-pads and DMA channel given in
- * the arguments. Depending on the IO-pads requested, the routing is done either using the 
+ * the arguments. Depending on the IO-pads requested, the routing is done either using the
  * IO_mux or using the GPIO matrix.
  *
  * @param host SPI peripheral to be routed
@@ -123,7 +151,7 @@ bool spicommon_dma_chan_free(int dma_chan);
  *              - ``SPICOMMON_BUSFLAG_MASTER``: Initialize I/O in master mode
  *              - ``SPICOMMON_BUSFLAG_SLAVE``: Initialize I/O in slave mode
  *              - ``SPICOMMON_BUSFLAG_NATIVE_PINS``: Pins set should match the iomux pins of the controller.
- *              - ``SPICOMMON_BUSFLAG_SCLK``, ``SPICOMMON_BUSFLAG_MISO``, ``SPICOMMON_BUSFLAG_MOSI``: 
+ *              - ``SPICOMMON_BUSFLAG_SCLK``, ``SPICOMMON_BUSFLAG_MISO``, ``SPICOMMON_BUSFLAG_MOSI``:
  *                  Make sure SCLK/MISO/MOSI is/are set to a valid GPIO. Also check output capability according to the mode.
  *              - ``SPICOMMON_BUSFLAG_DUAL``: Make sure both MISO and MOSI are output capable so that DIO mode is capable.
  *              - ``SPICOMMON_BUSFLAG_WPHD`` Make sure WP and HD are set to valid output GPIOs.
@@ -136,7 +164,7 @@ bool spicommon_dma_chan_free(int dma_chan);
  *              - ``SPICOMMON_BUSFLAG_DUAL``: The bus is capable with DIO mode.
  *              - ``SPICOMMON_BUSFLAG_WPHD`` The bus has WP and HD connected.
  *              - ``SPICOMMON_BUSFLAG_QUAD``: Combination of ``SPICOMMON_BUSFLAG_DUAL`` and ``SPICOMMON_BUSFLAG_WPHD``.
- * @return 
+ * @return
  *         - ESP_ERR_INVALID_ARG   if parameter is invalid
  *         - ESP_OK                on success
  */
@@ -145,10 +173,10 @@ esp_err_t spicommon_bus_initialize_io(spi_host_device_t host, const spi_bus_conf
 /**
  * @brief Free the IO used by a SPI peripheral
  * @deprecated Use spicommon_bus_free_io_cfg instead.
- * 
+ *
  * @param host SPI peripheral to be freed
- * 
- * @return 
+ *
+ * @return
  *         - ESP_ERR_INVALID_ARG   if parameter is invalid
  *         - ESP_OK                on success
  */
@@ -158,8 +186,8 @@ esp_err_t spicommon_bus_free_io(spi_host_device_t host) __attribute__((deprecate
  * @brief Free the IO used by a SPI peripheral
  *
  * @param bus_cfg Bus config struct which defines which pins to be used.
- * 
- * @return 
+ *
+ * @return
  *         - ESP_ERR_INVALID_ARG   if parameter is invalid
  *         - ESP_OK                on success
  */
@@ -240,10 +268,10 @@ typedef void(*dmaworkaround_cb_t)(void *arg);
  * @note In some (well-defined) cases in the ESP32 (at least rev v.0 and v.1), a SPI DMA channel will get confused. This can be remedied
  * by resetting the SPI DMA hardware in case this happens. Unfortunately, the reset knob used for thsi will reset _both_ DMA channels, and
  * as such can only done safely when both DMA channels are idle. These functions coordinate this.
- * 
+ *
  * Essentially, when a reset is needed, a driver can request this using spicommon_dmaworkaround_req_reset. This is supposed to be called
- * with an user-supplied function as an argument. If both DMA channels are idle, this call will reset the DMA subsystem and return true. 
- * If the other DMA channel is still busy, it will return false; as soon as the other DMA channel is done, however, it will reset the 
+ * with an user-supplied function as an argument. If both DMA channels are idle, this call will reset the DMA subsystem and return true.
+ * If the other DMA channel is still busy, it will return false; as soon as the other DMA channel is done, however, it will reset the
  * DMA subsystem and call the callback. The callback is then supposed to be used to continue the SPI drivers activity.
  *
  * @param dmachan DMA channel associated with the SPI host that needs a reset

+ 7 - 4
components/driver/spi_master.c

@@ -414,7 +414,7 @@ esp_err_t spi_bus_remove_device(spi_device_handle_t handle)
     SPI_CHECK(handle->host->cur_cs == NO_CS || handle->host->device[handle->host->cur_cs]!=handle, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
     SPI_CHECK(uxQueueMessagesWaiting(handle->ret_queue)==0, "Have unfinished transactions", ESP_ERR_INVALID_STATE);
 
-    //return 
+    //return
     int spics_io_num = handle->cfg.spics_io_num;
     if (spics_io_num >= 0) spicommon_cs_free_io(spics_io_num);
 
@@ -727,9 +727,12 @@ static void SPI_MASTER_ISR_ATTR spi_intr(void *arg)
         host->hw->user.usr_addr=addrlen?1:0;
         host->hw->user.usr_command=cmdlen?1:0;
 
-        // output command will be sent from bit 7 to 0 of command_value, and then bit 15 to 8 of the same register field.
-        uint16_t command = trans->cmd << (16-cmdlen);    //shift to MSB
-        host->hw->user2.usr_command_value = (command>>8)|(command<<8);  //swap the first and second byte
+        /* Output command will be sent from bit 7 to 0 of command_value, and
+         * then bit 15 to 8 of the same register field. Shift and swap to send
+         * more straightly.
+         */
+        host->hw->user2.usr_command_value = SPI_SWAP_DATA_TX(trans->cmd, cmdlen);
+
         // shift the address to MSB of addr (and maybe slv_wr_status) register.
         // output address will be sent from MSB to LSB of addr register, then comes the MSB to LSB of slv_wr_status register.
         if (addrlen>32) {

+ 37 - 14
docs/en/api-reference/peripherals/spi_master.rst

@@ -167,12 +167,35 @@ memcpy of temporary buffers.
 .. note::  Half duplex transactions with both read and write phases are not supported when using DMA. See
   :ref:`spi_known_issues` for details and workarounds.
 
-Sometimes, the amount of data is very small making it less than optimal allocating a separate buffer
-for it. If the data to be transferred is 32 bits or less, it can be stored in the transaction struct
-itself. For transmitted data, use the ``tx_data`` member for this and set the ``SPI_USE_TXDATA`` flag
-on the transmission. For received data, use ``rx_data`` and set ``SPI_USE_RXDATA``. In both cases, do
-not touch the ``tx_buffer`` or ``rx_buffer`` members, because they use the same memory locations
-as ``tx_data`` and ``rx_data``.
+Tips
+""""
+
+1. Transactions with small amount of data:
+    Sometimes, the amount of data is very small making it less than optimal allocating a separate buffer
+    for it. If the data to be transferred is 32 bits or less, it can be stored in the transaction struct
+    itself. For transmitted data, use the ``tx_data`` member for this and set the ``SPI_USE_TXDATA`` flag
+    on the transmission. For received data, use ``rx_data`` and set ``SPI_USE_RXDATA``. In both cases, do
+    not touch the ``tx_buffer`` or ``rx_buffer`` members, because they use the same memory locations
+    as ``tx_data`` and ``rx_data``.
+
+2. Transactions with integers other than uint8_t
+    The SPI peripheral reads and writes the memory byte-by-byte. By default,
+    the SPI works at MSB first mode, each bytes are sent or received from the
+    MSB to the LSB. However, if you want to send data with length which is
+    not multiples of 8 bits, unused bits are sent.
+
+    E.g. you write ``uint8_t data = 0x15`` (00010101B), and set length to
+    only 5 bits, the sent data is ``00010B`` rather than expected ``10101B``.
+
+    Moreover, ESP32 is a little-endian chip whose lowest byte is stored at
+    the very beginning address for uint16_t and uint32_t variables. Hence if
+    a uint16_t is stored in the memory, it's bit 7 is first sent, then bit 6
+    to 0, then comes its bit 15 to bit 8.
+
+    To send data other than uint8_t arrays, macros ``SPI_SWAP_DATA_TX`` is
+    provided to shift your data to the MSB and swap the MSB to the lowest
+    address; while ``SPI_SWAP_DATA_RX`` can be used to swap received data
+    from the MSB to it's correct place.
 
 Speed and Timing Considerations
 -------------------------------
@@ -286,10 +309,10 @@ And if the host only writes, the *dummy bit workaround* is not used and the freq
 | 40                | 80               |
 +-------------------+------------------+
 
-The spi master driver can work even if the *input delay* in the ``spi_device_interface_config_t`` is set to 0. 
-However, setting a accurate value helps to: (1) calculate the frequency limit in full duplex mode, and (2) compensate 
-the timing correctly by dummy bits in half duplex mode. You may find the maximum data valid time after the launch edge 
-of SPI clocks in the AC characteristics chapter of the device specifications, or measure the time on a oscilloscope or 
+The spi master driver can work even if the *input delay* in the ``spi_device_interface_config_t`` is set to 0.
+However, setting a accurate value helps to: (1) calculate the frequency limit in full duplex mode, and (2) compensate
+the timing correctly by dummy bits in half duplex mode. You may find the maximum data valid time after the launch edge
+of SPI clocks in the AC characteristics chapter of the device specifications, or measure the time on a oscilloscope or
 logic analyzer.
 
 .. wavedrom don't support rendering pdflatex till now(1.3.1), so we use the png here
@@ -327,18 +350,18 @@ Some typical delays are shown in the following table:
 | chip, 12.5ns sample delay included.   |
 +---------------------------------------+
 
-The MISO path delay(tv), consists of slave *input delay* and master *GPIO matrix delay*, finally determines the 
-frequency limit, above which the full duplex mode will not work, or dummy bits are used in the half duplex mode. The 
+The MISO path delay(tv), consists of slave *input delay* and master *GPIO matrix delay*, finally determines the
+frequency limit, above which the full duplex mode will not work, or dummy bits are used in the half duplex mode. The
 frequency limit is:
 
     *Freq limit[MHz] = 80 / (floor(MISO delay[ns]/12.5) + 1)*
 
-The figure below shows the relations of frequency limit against the input delay. 2 extra apb clocks should be counted 
+The figure below shows the relations of frequency limit against the input delay. 2 extra apb clocks should be counted
 into the MISO delay if the GPIO matrix in the master is used.
 
 .. image:: /../_static/spi_master_freq_tv.png
 
-Corresponding frequency limit for different devices with different *input delay* are shown in the following 
+Corresponding frequency limit for different devices with different *input delay* are shown in the following
 table:
 
 +--------+------------------+----------------------+-------------------+