diff options
-rw-r--r-- | docs/source/lang/api/hardwarespi.rst | 215 | ||||
-rw-r--r-- | docs/source/spi.rst | 6 | ||||
-rw-r--r-- | examples/test-spi-roundtrip.cpp | 197 | ||||
-rw-r--r-- | libmaple/spi.c | 333 | ||||
-rw-r--r-- | libmaple/spi.h | 495 | ||||
-rw-r--r-- | wirish/boards/maple.h | 13 | ||||
-rw-r--r-- | wirish/boards/maple_RET6.h | 10 | ||||
-rw-r--r-- | wirish/boards/maple_mini.h | 10 | ||||
-rw-r--r-- | wirish/boards/maple_native.cpp | 2 | ||||
-rw-r--r-- | wirish/boards/maple_native.h | 21 | ||||
-rw-r--r-- | wirish/comm/HardwareSPI.cpp | 335 | ||||
-rw-r--r-- | wirish/comm/HardwareSPI.h | 178 |
12 files changed, 1389 insertions, 426 deletions
diff --git a/docs/source/lang/api/hardwarespi.rst b/docs/source/lang/api/hardwarespi.rst index 53a225d..289ded5 100644 --- a/docs/source/lang/api/hardwarespi.rst +++ b/docs/source/lang/api/hardwarespi.rst @@ -5,160 +5,161 @@ HardwareSPI =========== -This class is used for creating objects to manage the Maple's built-in -SPI ports. The Maple has two SPI ports. The relevant pins -corresponding to each port's logic signals are documented in the -following table (and on the Maple silkscreen): +This page describes how to use the built-in SPI ports. It does not +describe the SPI protocol itself. For more information about SPI, see +the :ref:`SPI reference <spi>`. -.. _lang-hardwarespi-pinout: +.. contents:: Contents + :local: -.. list-table:: - :header-rows: 1 +Getting Started +--------------- - * - Port number - - NSS - - MOSI - - MISO - - SCK +.. TODO [0.1.0] Add a note about calling disableDebugPorts() when +.. using SPI3 on Maple Native - * - 1 - - 10 - - 11 - - 12 - - 13 +In order to get started, you'll first need to define a ``HardwareSPI`` +variable, which you'll use to control the SPI port. Do this by +putting the line "``HardwareSPI spi(number);``" with your variables, +where ``number`` is the SPI port's number. - * - 2 - - 31 - - 32 - - 33 - - 34 +Here's an example (we'll fill in :ref:`setup() <lang-setup>` and +:ref:`loop() <lang-loop>` later):: -If you use a SPI port, you cannot simultaneously use its associated -pins for other purposes. + // Use SPI port number 1 + HardwareSPI spi(1); -Library Documentation ---------------------- + void setup() { + // Your setup code + } + + void loop() { + // Do stuff with SPI + } -Using the SPI Class -^^^^^^^^^^^^^^^^^^^ +Turning the SPI Port On +----------------------- -You can declare that you want to use SPI in your sketch by putting -``HardwareSPI Spi(number);`` with your variables, where ``number`` is -1 or 2, depending on which SPI you want to use. Then you can use the -functions described in the next section. For example:: +Now it's time to turn your SPI port on. Do this with the ``begin()`` +function (an example is given below). - // Use SPI 1 - HardwareSpi Spi(1); +.. FIXME [0.0.10] Breathe doesn't include the class; fix & submit pull req + +.. doxygenfunction:: HardwareSPI::begin + +The speed at which the SPI port communicates is configured using a +``SPIFrequency`` value: + +.. FIXME [0.1.0] Breathe's enum output is enormous; shrink & submit pull req + +.. doxygenenum:: SPIFrequency + +.. note:: Due to hardware issues, you can't use the frequency + ``SPI_140_625KHz`` with SPI port 1. + +You'll need to determine the correct values for ``frequency``, +``bitOrder``, and ``mode`` yourself, by consulting the datasheet for +the device you're communicating with. Continuing our example from +before, we'll add a call to ``begin()`` to our ``setup()``:: + + // Use SPI port number 1 + HardwareSPI spi(1); void setup() { - Spi.begin(SPI_18MHZ); + // Turn on the SPI port + spi.begin(SPI_18MHZ, MSBFIRST, 0); } void loop() { - // Get the next byte from the peripheral - uint8 byte = Spi.recv(); + // Do stuff with SPI } -HardwareSPI Class Reference -^^^^^^^^^^^^^^^^^^^^^^^^^^^ +If you call ``begin()`` with no arguments (as in "``spi.begin();``"), +it's the same as if you wrote "``spi.begin(SPI_1_125MHZ, MSBFIRST, +0);``". -.. cpp:class:: HardwareSPI +Communicating Over SPI +---------------------- - Class for interacting with SPI. +Now that you've got your SPI port set up, it's time to start +communicating. You can send data using ``HardwareSPI::write()``, +receive data using ``HardwareSPI::read()``, and do both using +``HardwareSPI::transfer()``. -.. cpp:function:: HardwareSPI::HardwareSPI(uint32 spi_num) +.. cpp:function:: void HardwareSPI::write(byte data) - Construct an object for managing a SPI peripheral. ``spi_num`` - must be 1 or 2; see the :ref:`table above - <lang-hardwarespi-pinout>` for pinout information. + Send a single byte of data. -.. cpp:function:: void HardwareSPI::begin(SPIFrequency freq, uint32 endianness, uint32 mode) + **Parameters**: - Configure the baudrate of the given SPI port and set up the header - pins appropriately. + - ``data``: Byte to send - Parameters: +.. cpp:function:: byte HardwareSPI::read() - - ``freq``: one of the ``SPIFrequency`` values, given :ref:`below - <lang-hardwarespi-spifrequency>`. + Get the next available, unread byte. If there aren't any unread + bytes, this function will wait until one is received. - - ``endianness``: either ``LSBFIRST`` (little-endian) or - ``MSBFIRST`` (big-endian). +.. cpp:function:: byte HardwareSPI::transmit(byte data) - - ``mode``: one of 0, 1, 2, or 3, and specifies which SPI mode is - used. The mode number determines a combination of polarity and - phase according to the following table: + Send a byte, then return the next byte received. - .. list-table:: - :header-rows: 1 + **Parameters:** - * - Mode - - Polarity - - Phase + - ``data``: Byte to send - * - 0 - - 0 - - 0 + **Returns:** Next unread byte - * - 1 - - 0 - - 1 +Continuing our example from before, let's send a number over SPI and +print out whatever we get back over :ref:`lang-serialusb`:: - * - 2 - - 1 - - 0 + // Use SPI port number 1 + HardwareSPI spi(1); - * - 3 - - 1 - - 1 + void setup() { + // Turn on the SPI port + spi.begin(SPI_18MHZ, MSBFIRST, 0); + } - For more information on polarity and phase, see the - :ref:`external references, below <lang-hardwarespi-seealso>`. + void loop() { + // Send 245 over SPI, and wait for a response. + spi.write(245); + byte response = spi.read(); + // Print out the response received. + SerialUSB.print("response: "); + SerialUSB.println(response, DEC); + } + +HardwareSPI Class Reference +--------------------------- + +There are a number of other things you can accomplish with your +``spi`` object. A full function listing follows. -.. cpp:function:: void HardwareSPI::begin() +.. doxygenclass:: HardwareSPI + :members: HardwareSPI, begin, beginSlave, end, read, write, transfer - A convenience ``begin()``, equivalent to ``begin(SPI_1_125MHZ, - MSBFIRST, 0)``. +Deprecated Functions +-------------------- -.. cpp:function:: uint8 HardwareSpi::send(uint8 *data, uint32 length) +The following functions are defined for now, but they have been +deprecated, and will be removed in a future Maple IDE release. You +shouldn't use them in new programs, and you should change any of your +programs which do use them to the up-to-date functions discussed +above. + +.. cpp:function:: uint8 HardwareSPI::send(uint8 *data, uint32 length) Writes ``data`` into the port buffer to be transmitted as soon as possible, where ``length`` is the number of bytes to send from ``data``. Returns the last byte shifted back from slave. -.. cpp:function:: uint8 HardwareSpi::send(uint8 data) +.. cpp:function:: uint8 HardwareSPI::send(uint8 data) Writes the single byte ``data`` into the port buffer to be transmitted as soon as possible. Returns the data byte shifted back from the slave. -.. cpp:function:: uint8 HardwareSpi::recv() +.. cpp:function:: uint8 HardwareSPI::recv() Reads a byte from the peripheral. Returns the next byte in the buffer. - -SPI Speeds -^^^^^^^^^^ - -.. _lang-hardwarespi-spifrequency: - -The possible SPI speeds are configured using the ``SPIFrequency`` enum: - -.. doxygenenum:: SPIFrequency - -.. _lang-hardwarespi-seealso: - -See Also --------- - -* `Wikipedia Article on Serial Peripheral Interface Bus (SPI) - <http://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus>`_ -* `Arduino reference on SPI - <http://www.arduino.cc/playground/Code/Spi>`_ -* `Hardcore SPI on Arduino <http://klk64.com/arduino-spi/>`_ by kik64 -* STMicro documentation for STM32F103RB microcontroller: - - * `Datasheet <http://www.st.com/stonline/products/literature/ds/13587.pdf>`_ (pdf) - * `Reference Manual <http://www.st.com/stonline/products/literature/rm/13902.pdf>`_ (pdf) - - diff --git a/docs/source/spi.rst b/docs/source/spi.rst index 2da4bf8..dd9f1f5 100644 --- a/docs/source/spi.rst +++ b/docs/source/spi.rst @@ -8,12 +8,8 @@ The Serial Peripheral Interface Bus (SPI) is a serial data transfer protocol useful for interacting with a wide variety of hardware peripherals. -The Maple has two SPI ports. The first has NSS on D10, MOSI on -D11, MISO on D12, and SCK on D13. The second has NSS on D31, SCK on -D32, MISO on D33, and MOSI on D34. - The public libmaple API for managing the SPI ports is the -:ref:`HardwareSpi <lang-hardwarespi>` class. +:ref:`HardwareSPI <lang-hardwarespi>` class. Recommended Reading ------------------- diff --git a/examples/test-spi-roundtrip.cpp b/examples/test-spi-roundtrip.cpp new file mode 100644 index 0000000..257303b --- /dev/null +++ b/examples/test-spi-roundtrip.cpp @@ -0,0 +1,197 @@ +/* + * Polling SPI loopback test. + * + * Bob is nowhere to be found, so Alice decides to talk to herself. + * + * Instructions: Connect SPI2 (Alice) to herself (i.e., MISO to MOSI). + * Connect to Alice via Serial2 at 115200 baud. Press any key to start. + * + * Alice will talk to herself for a little while. The sketch will + * report if Alice can't hear anything she says. She'll then start + * talking forever at various frequencies, bit orders, and modes. Use + * an oscilloscope to make sure she's not trying to lie about any of + * those things. + * + * This file is released into the public domain. + * + * Author: Marti Bolivar <mbolivar@leaflabs.com> + */ + +#include "wirish.h" + +HardwareSPI alice(2); + +#define NFREQS 8 +const SPIFrequency spi_freqs[] = { + SPI_140_625KHZ, + SPI_281_250KHZ, + SPI_562_500KHZ, + SPI_1_125MHZ, + SPI_2_25MHZ, + SPI_4_5MHZ, + SPI_9MHZ, + SPI_18MHZ, +}; + +#define TEST_BUF_SIZE 10 +uint8 test_buf[TEST_BUF_SIZE] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + +void bad_assert(const char* file, int line, const char* exp) { + Serial2.println(); + Serial2.print("ERROR: FAILED ASSERT("); + Serial2.print(exp); + Serial2.print("): "); + Serial2.print(file); + Serial2.print(": "); + Serial2.println(line); + throb(); +} + +#undef ASSERT +#define ASSERT(exp) \ + if (exp) { \ + } else { \ + bad_assert(__FILE__, __LINE__, #exp); \ + } + +void haveConversation(uint32 bitOrder); +void soliloquies(uint32 bitOrder); + +void setup() { + pinMode(BOARD_LED_PIN, OUTPUT); + Serial2.begin(115200); + Serial2.println(); + Serial2.print("** SPI test ready. Press any key to start."); + while (!Serial2.available()) + ; + Serial2.read(); +} + +void loop() { + Serial2.println("** Having a conversation, MSB first"); + haveConversation(MSBFIRST); + + Serial2.println("** Having a conversation, LSB first"); + haveConversation(LSBFIRST); + + Serial2.println(); + Serial2.println("*** All done! It looks like everything worked."); + Serial2.println(); + + Serial2.println("** Alice will now wax eloquent in various styles. " + "Press any key for the next configuration."); + soliloquies(MSBFIRST); + soliloquies(LSBFIRST); + + while (true) + ; +} + +void printFrequencyString(SPIFrequency frequency); +void chat(SPIFrequency frequency, uint32 bitOrder, uint32 mode); + +void haveConversation(uint32 bitOrder) { + for (int f = 0; f < NFREQS; f++) { + for (int mode = 0; mode < 4; mode++) { + chat(spi_freqs[f], bitOrder, mode); + delay(10); + } + } +} + +void chat(SPIFrequency frequency, uint32 bitOrder, uint32 mode) { + Serial2.print("Having a chat.\tFrequency: "); + printFrequencyString(frequency); + Serial2.print(",\tbitOrder: "); + Serial2.print(bitOrder == MSBFIRST ? "MSB" : "LSB"); + Serial2.print(",\tmode: "); + Serial2.print(mode); + Serial2.print("."); + + Serial2.print(" [1] "); + alice.begin(frequency, bitOrder, mode); + + Serial2.print(" [2] "); + uint32 txed = 0; + while (txed < TEST_BUF_SIZE) { + ASSERT(alice.transfer(test_buf[txed]) == test_buf[txed]); + txed++; + } + + Serial2.print(" [3] "); + alice.end(); + + Serial2.println(" ok."); +} + +void soliloquy(SPIFrequency freq, uint32 bitOrder, uint32 mode); + +void soliloquies(uint32 bitOrder) { + for (int f = 0; f < NFREQS; f++) { + for (int mode = 0; mode < 4; mode++) { + soliloquy(spi_freqs[f], bitOrder, mode); + } + } +} + +void soliloquy(SPIFrequency frequency, uint32 bitOrder, uint32 mode) { + const uint8 repeat = 0xAE; + Serial2.print("Alice is giving a soliloquy (repeating 0x"); + Serial2.print(repeat, HEX); + Serial2.print("). Frequency: "); + printFrequencyString(frequency); + Serial2.print(", bitOrder: "); + Serial2.print(bitOrder == MSBFIRST ? "big-endian" : "little-endian"); + Serial2.print(", SPI mode: "); + Serial2.println(mode); + + alice.begin(frequency, bitOrder, mode); + while (!Serial2.available()) { + alice.write(repeat); + delayMicroseconds(200); + } + Serial2.read(); +} + +void printFrequencyString(SPIFrequency frequency) { + switch (frequency) { + case SPI_18MHZ: + Serial2.print("18 MHz"); + break; + case SPI_9MHZ: + Serial2.print("9 MHz"); + break; + case SPI_4_5MHZ: + Serial2.print("4.5 MHz"); + break; + case SPI_2_25MHZ: + Serial2.print("2.25 MHZ"); + break; + case SPI_1_125MHZ: + Serial2.print("1.125 MHz"); + break; + case SPI_562_500KHZ: + Serial2.print("562.500 KHz"); + break; + case SPI_281_250KHZ: + Serial2.print("281.250 KHz"); + break; + case SPI_140_625KHZ: + Serial2.print("140.625 KHz"); + break; + } +} + +// Force init to be called *first*, i.e. before static object allocation. +// Otherwise, statically allocated objects that need libmaple may fail. +__attribute__((constructor)) void premain() { + init(); +} + +int main(void) { + setup(); + while (true) { + loop(); + } + return 0; +} diff --git a/libmaple/spi.c b/libmaple/spi.c index 7bdc18a..2fbc2ac 100644 --- a/libmaple/spi.c +++ b/libmaple/spi.c @@ -3,161 +3,250 @@ * * Copyright (c) 2010 Perry Hung. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. *****************************************************************************/ /** - * @brief libmaple serial peripheral interface (SPI) definitions - * - * Initial implementation for the SPI interface. - * - * This driver implements only the bare essentials of a polling driver at the - * moment. Master mode only, 8-bit data frames, and polling. - * - * The caller is responsible for controlling the chip select line. - * - * TODO: interrupt based driver, DMA. - * + * @file spi.c + * @author Marti Bolivar <mbolivar@leaflabs.com> + * @brief Serial Peripheral Interface (SPI) support. + * Currently, there is no Integrated Interchip Sound (I2S) support. */ -#include "libmaple.h" -#include "gpio.h" -#include "rcc.h" #include "spi.h" +#include "bitband.h" -typedef struct spi_dev { - SPI *base; - gpio_dev *gpio_d; - uint8 sck_pin; - uint8 miso_pin; - uint8 mosi_pin; -} spi_dev; - -spi_dev spi_dev1 = { - .base = (SPI*)SPI1_BASE, - .gpio_d = NULL, - .sck_pin = 5, - .miso_pin = 6, - .mosi_pin = 7 -}; +/* + * SPI devices + */ -spi_dev spi_dev2 = { - .base = (SPI*)SPI2_BASE, - .gpio_d = NULL, - .sck_pin = 13, - .miso_pin = 14, - .mosi_pin = 15 +static spi_dev spi1 = { + .regs = SPI1_BASE, + .clk_id = RCC_SPI1, + .irq_num = NVIC_SPI1, }; +spi_dev *SPI1 = &spi1; -static void spi_gpio_cfg(const spi_dev *dev); +static spi_dev spi2 = { + .regs = SPI2_BASE, + .clk_id = RCC_SPI2, + .irq_num = NVIC_SPI2, +}; +spi_dev *SPI2 = &spi2; -/** - * @brief Initialize a spi peripheral - * @param spi_num which spi to turn on, SPI1 or SPI2? - * @param prescale prescale factor on the input clock. - * @param endian data frame format (LSBFIRST?) - * @param mode SPI mode number - */ -void spi_init(uint32 spi_num, - uint32 prescale, - uint32 endian, - uint32 mode) { - ASSERT(spi_num == 1 || spi_num == 2); - ASSERT(mode < 4); - - SPI *spi; - uint32 cr1 = 0; - - switch (spi_num) { - case 1: - /* limit to 18 mhz max speed */ - ASSERT(prescale != CR1_BR_PRESCALE_2); - spi = (SPI*)SPI1_BASE; - rcc_clk_enable(RCC_SPI1); - spi_dev1.gpio_d = GPIOA; - spi_gpio_cfg(&spi_dev1); - break; - case 2: - spi = (SPI*)SPI2_BASE; - rcc_clk_enable(RCC_SPI2); - spi_dev1.gpio_d = GPIOB, - spi_gpio_cfg(&spi_dev2); - break; - } +#ifdef STM32_HIGH_DENSITY +static spi_dev spi3 = { + .regs = SPI3_BASE, + .clk_id = RCC_SPI3, + .irq_num = NVIC_SPI3, +}; +spi_dev *SPI3 = &spi3; +#endif - cr1 = prescale | endian | mode | CR1_MSTR | CR1_SSI | CR1_SSM; - spi->CR1 = cr1; +/* + * SPI convenience routines + */ - /* Peripheral enable */ - spi->CR1 |= CR1_SPE; +/** + * @brief Initialize and reset a SPI device. + * @param dev Device to initialize and reset. + */ +void spi_init(spi_dev *dev) { + rcc_clk_enable(dev->clk_id); + rcc_reset_dev(dev->clk_id); } /** - * @brief SPI synchronous 8-bit write, blocking. - * @param spi_num which spi to send on - * @return data shifted back from the slave + * @brief Configure GPIO bit modes for use as a SPI bus master's pins. + * @param nss_dev NSS pin's GPIO device + * @param comm_dev SCK, MISO, MOSI pins' GPIO device + * @param nss_bit NSS pin's GPIO bit on nss_dev + * @param sck_bit SCK pin's GPIO bit on comm_dev + * @param miso_bit MISO pin's GPIO bit on comm_dev + * @param mosi_bit MOSI pin's GPIO bit on comm_dev */ -uint8 spi_tx_byte(uint32 spi_num, uint8 data) { - SPI *spi; +void spi_master_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit) { + gpio_set_mode(nss_dev, nss_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, sck_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, miso_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, mosi_bit, GPIO_AF_OUTPUT_PP); +} - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @brief Configure GPIO bit modes for use as a SPI bus slave's pins. + * @param nss_dev NSS pin's GPIO device + * @param comm_dev SCK, MISO, MOSI pins' GPIO device + * @param nss_bit NSS pin's GPIO bit on nss_dev + * @param sck_bit SCK pin's GPIO bit on comm_dev + * @param miso_bit MISO pin's GPIO bit on comm_dev + * @param mosi_bit MOSI pin's GPIO bit on comm_dev + */ +void spi_slave_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit) { + gpio_set_mode(nss_dev, nss_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, sck_bit, GPIO_INPUT_FLOATING); + gpio_set_mode(comm_dev, miso_bit, GPIO_AF_OUTPUT_PP); + gpio_set_mode(comm_dev, mosi_bit, GPIO_INPUT_FLOATING); +} - while (!(spi->SR & SR_TXE)) - ; +static void spi_reconfigure(spi_dev *dev, uint32 cr1_config); - spi->DR = data; +/** + * @brief Configure and enable a SPI device as bus master. + * + * The device's peripheral will be disabled before being reconfigured. + * + * @param dev Device to configure as bus master + * @param baud Bus baud rate + * @param mode SPI mode + * @param flags Logical OR of spi_cfg_flag values. + * @see spi_cfg_flag + */ +void spi_master_enable(spi_dev *dev, + spi_baud_rate baud, + spi_mode mode, + uint32 flags) { + spi_reconfigure(dev, baud | flags | SPI_CR1_MSTR | mode); +} - while (!(spi->SR & SR_RXNE)) - ; +/** + * @brief Configure and enable a SPI device as a bus slave. + * + * The device's peripheral will be disabled before being reconfigured. + * + * @param dev Device to configure as a bus slave + * @param mode SPI mode + * @param flags Logical OR of spi_cfg_flag values. + * @see spi_cfg_flag + */ +void spi_slave_enable(spi_dev *dev, spi_mode mode, uint32 flags) { + spi_reconfigure(dev, flags | mode); +} - return spi->DR; +/** + * @brief Nonblocking SPI transmit. + * @param dev SPI port to use for transmission + * @param buf Buffer to transmit. The sizeof buf's elements are + * inferred from the dev's data frame format (i.e., are + * correctly treated as 8-bit or 16-bit quantities). + * @param len Maximum number of elements to transmit. + * @return Number of elements transmitted. + */ +uint32 spi_tx(spi_dev *dev, const void *buf, uint32 len) { + spi_reg_map *regs = dev->regs; + uint32 txed = 0; + if (spi_dff(dev) == SPI_DFF_16_BIT) { + const uint8 *buf8 = (const uint8*)buf; + while ((regs->SR & SPI_SR_TXE) && (txed < len)) { + regs->DR = buf8[txed++]; + } + } else { + const uint16 *buf16 = (const uint16*)buf; + while ((regs->SR & SPI_SR_TXE) && (txed < len)) { + regs->DR = buf16[txed++]; + } + } + return txed; } -uint8 spi_tx(uint32 spi_num, uint8 *buf, uint32 len) { - SPI *spi; - uint32 i = 0; - uint8 rc; +/** + * @brief Call a function on each SPI port + * @param fn Function to call. + */ +void spi_foreach(void (*fn)(spi_dev*)) { + fn(SPI1); + fn(SPI2); +#ifdef STM32_HIGH_DENSITY + fn(SPI3); +#endif +} - ASSERT(spi_num == 1 || spi_num == 2); - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; +/** + * @brief Enable a SPI peripheral + * @param dev Device to enable + */ +void spi_peripheral_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR1, SPI_CR1_SPE_BIT, 1); +} - if (!len) { - return 0; - } +/** + * @brief Disable a SPI peripheral + * @param dev Device to disable + */ +void spi_peripheral_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR1, SPI_CR1_SPE_BIT, 0); +} - while (i < len) { - while (!(spi->SR & SR_TXE)) - ; +/** + * @brief Enable DMA requests whenever the transmit buffer is empty + * @param dev SPI device on which to enable TX DMA requests + */ +void spi_tx_dma_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_TXDMAEN_BIT, 1); +} - spi->DR = buf[i]; +/** + * @brief Disable DMA requests whenever the transmit buffer is empty + * @param dev SPI device on which to disable TX DMA requests + */ +void spi_tx_dma_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_TXDMAEN_BIT, 0); +} - while (!(spi->SR & SR_RXNE)) - ; +/** + * @brief Enable DMA requests whenever the receive buffer is empty + * @param dev SPI device on which to enable RX DMA requests + */ +void spi_rx_dma_enable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_RXDMAEN_BIT, 1); +} - rc = spi->DR; - i++; - } - return rc; +/** + * @brief Disable DMA requests whenever the receive buffer is empty + * @param dev SPI device on which to disable RX DMA requests + */ +void spi_rx_dma_disable(spi_dev *dev) { + bb_peri_set_bit(&dev->regs->CR2, SPI_CR2_RXDMAEN_BIT, 0); } -static void spi_gpio_cfg(const spi_dev *dev) { - gpio_set_mode(dev->gpio_d, dev->sck_pin, GPIO_AF_OUTPUT_PP); - gpio_set_mode(dev->gpio_d, dev->miso_pin, GPIO_AF_OUTPUT_PP); - gpio_set_mode(dev->gpio_d, dev->mosi_pin, GPIO_AF_OUTPUT_PP); +/* + * SPI auxiliary routines + */ + +static void spi_reconfigure(spi_dev *dev, uint32 cr1_config) { + spi_irq_disable(dev, SPI_INTERRUPTS_ALL); + spi_peripheral_disable(dev); + dev->regs->CR1 = cr1_config; + spi_peripheral_enable(dev); } + +/* + * IRQ handlers (TODO) + */ diff --git a/libmaple/spi.h b/libmaple/spi.h index 5ebf52d..e2820dd 100644 --- a/libmaple/spi.h +++ b/libmaple/spi.h @@ -24,99 +24,440 @@ /** * @file spi.h - * @brief libmaple serial peripheral interface (SPI) prototypes and - * declarations + * @author Marti Bolivar <mbolivar@leaflabs.com> + * @brief Serial Peripheral Interface (SPI) and Integrated + * Interchip Sound (I2S) peripheral support. + * + * I2S support is currently limited to register maps and bit definitions. */ #ifndef _SPI_H_ #define _SPI_H_ #include "libmaple_types.h" +#include "rcc.h" +#include "nvic.h" +#include "gpio.h" #include "util.h" #ifdef __cplusplus extern "C" { #endif -/* peripheral addresses */ -#define SPI1_BASE 0x40013000 -#define SPI2_BASE 0x40003800 -#define SPI3_BASE 0x40003C00 - -/* baud rate prescaler bits */ -#define CR1_BR 0x00000038 -#define CR1_BR_PRESCALE_2 0x00000000 -#define CR1_BR_PRESCALE_4 0x00000008 -#define CR1_BR_PRESCALE_8 0x00000010 -#define CR1_BR_PRESCALE_16 0x00000018 -#define CR1_BR_PRESCALE_32 0x00000020 -#define CR1_BR_PRESCALE_64 0x00000028 -#define CR1_BR_PRESCALE_128 0x00000030 -#define CR1_BR_PRESCALE_256 0x00000038 - -#define CR1_LSBFIRST BIT(7) // data frame format -#define CR1_MSTR BIT(2) // master selection -#define CR1_SSM BIT(9) // software slave management -#define CR1_SSI BIT(8) // internal slave select -#define CR1_SPE BIT(6) // peripheral enable - -/* Status register bits */ -#define SR_RXNE BIT(0) // rx buffer not empty -#define SR_TXE BIT(1) // transmit buffer empty -#define SR_BSY BIT(7) // busy flag - -typedef struct SPI { - __io uint16 CR1; - uint16 pad0; - __io uint8 CR2; - uint8 pad1[3]; - __io uint8 SR; - uint8 pad2[3]; - __io uint16 DR; - uint16 pad3; - __io uint16 CRCPR; - uint16 pad4; - __io uint16 RXCRCR; - uint16 pad5; - __io uint16 TXCRCR; - uint16 pad6; -} SPI; - -enum { - SPI_MSBFIRST = 0, - SPI_LSBFIRST = BIT(7), -}; - -enum { - SPI_PRESCALE_2 = (0x0 << 3), - SPI_PRESCALE_4 = (0x1 << 3), - SPI_PRESCALE_8 = (0x2 << 3), - SPI_PRESCALE_16 = (0x3 << 3), - SPI_PRESCALE_32 = (0x4 << 3), - SPI_PRESCALE_64 = (0x5 << 3), - SPI_PRESCALE_128 = (0x6 << 3), - SPI_PRESCALE_256 = (0x7 << 3) -}; - -void spi_init(uint32 spi_num, - uint32 prescale, - uint32 endian, - uint32 mode); -uint8 spi_tx_byte(uint32 spi_num, uint8 data); -uint8 spi_tx(uint32 spi_num, uint8 *buf, uint32 len); - -static inline uint8 spi_rx(uint32 spi_num) { - SPI *spi; - - ASSERT(spi_num == 1 || spi_num == 2); - spi = (spi_num == 1) ? (SPI*)SPI1_BASE : (SPI*)SPI2_BASE; - - return spi->DR; +/* + * Register maps + */ + +/** SPI register map type. */ +typedef struct spi_reg_map { + __io uint32 CR1; /**< Control register 1 */ + __io uint32 CR2; /**< Control register 2 */ + __io uint32 SR; /**< Status register */ + __io uint32 DR; /**< Data register */ + __io uint32 CRCPR; /**< CRC polynomial register */ + __io uint32 RXCRCR; /**< RX CRC register */ + __io uint32 TXCRCR; /**< TX CRC register */ + __io uint32 I2SCFGR; /**< I2S configuration register */ + __io uint32 I2SPR; /**< I2S prescaler register */ +} spi_reg_map; + +/** SPI1 register map base pointer */ +#define SPI1_BASE ((struct spi_reg_map*)0x40013000) +/** SPI2 register map base pointer */ +#define SPI2_BASE ((struct spi_reg_map*)0x40003800) +/** SPI3 register map base pointer */ +#define SPI3_BASE ((struct spi_reg_map*)0x40003C00) + +/* + * Register bit definitions + */ + +/* Control register 1 */ + +#define SPI_CR1_BIDIMODE_BIT 15 +#define SPI_CR1_BIDIOE_BIT 14 +#define SPI_CR1_CRCEN_BIT 13 +#define SPI_CR1_CRCNEXT_BIT 12 +#define SPI_CR1_DFF_BIT 11 +#define SPI_CR1_RXONLY_BIT 10 +#define SPI_CR1_SSM_BIT 9 +#define SPI_CR1_SSI_BIT 8 +#define SPI_CR1_LSBFIRST_BIT 7 +#define SPI_CR1_SPE_BIT 6 +#define SPI_CR1_MSTR_BIT 2 +#define SPI_CR1_CPOL_BIT 1 +#define SPI_CR1_CPHA_BIT 0 + +#define SPI_CR1_BIDIMODE BIT(SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIMODE_2_LINE (0x0 << SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIMODE_1_LINE (0x1 << SPI_CR1_BIDIMODE_BIT) +#define SPI_CR1_BIDIOE BIT(SPI_CR1_BIDIOE_BIT) +#define SPI_CR1_CRCEN BIT(SPI_CR1_CRCEN_BIT) +#define SPI_CR1_CRCNEXT BIT(SPI_CR1_CRCNEXT_BIT) +#define SPI_CR1_DFF BIT(SPI_CR1_DFF_BIT) +#define SPI_CR1_DFF_8_BIT (0x0 << SPI_CR1_DFF_BIT) +#define SPI_CR1_DFF_16_BIT (0x1 << SPI_CR1_DFF_BIT) +#define SPI_CR1_RXONLY BIT(SPI_CR1_RXONLY_BIT) +#define SPI_CR1_SSM BIT(SPI_CR1_SSM_BIT) +#define SPI_CR1_SSI BIT(SPI_CR1_SSI_BIT) +#define SPI_CR1_LSBFIRST BIT(SPI_CR1_LSBFIRST_BIT) +#define SPI_CR1_SPE BIT(SPI_CR1_SPE_BIT) +#define SPI_CR1_BR (0x7 << 3) +#define SPI_CR1_BR_PCLK_DIV_2 (0x0 << 3) +#define SPI_CR1_BR_PCLK_DIV_4 (0x1 << 3) +#define SPI_CR1_BR_PCLK_DIV_8 (0x2 << 3) +#define SPI_CR1_BR_PCLK_DIV_16 (0x3 << 3) +#define SPI_CR1_BR_PCLK_DIV_32 (0x4 << 3) +#define SPI_CR1_BR_PCLK_DIV_64 (0x5 << 3) +#define SPI_CR1_BR_PCLK_DIV_128 (0x6 << 3) +#define SPI_CR1_BR_PCLK_DIV_256 (0x7 << 3) +#define SPI_CR1_MSTR BIT(SPI_CR1_MSTR_BIT) +#define SPI_CR1_CPOL BIT(SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPOL_LOW (0x0 << SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPOL_HIGH (0x1 << SPI_CR1_CPOL_BIT) +#define SPI_CR1_CPHA BIT(SPI_CR1_CPHA_BIT) + +/* Control register 2 */ + +/* RM0008-ism: SPI CR2 has "TXDMAEN" and "RXDMAEN" bits, while the + * USARTs have CR3 "DMAR" and "DMAT" bits. */ + +#define SPI_CR2_TXEIE_BIT 7 +#define SPI_CR2_RXNEIE_BIT 6 +#define SPI_CR2_ERRIE_BIT 5 +#define SPI_CR2_SSOE_BIT 2 +#define SPI_CR2_TXDMAEN_BIT 1 +#define SPI_CR2_RXDMAEN_BIT 0 + +#define SPI_CR2_TXEIE BIT(SPI_CR2_TXEIE_BIT) +#define SPI_CR2_RXNEIE BIT(SPI_CR2_RXNEIE_BIT) +#define SPI_CR2_ERRIE BIT(SPI_CR2_ERRIE_BIT) +#define SPI_CR2_SSOE BIT(SPI_CR2_SSOE_BIT) +#define SPI_CR2_TXDMAEN BIT(SPI_CR2_TXDMAEN_BIT) +#define SPI_CR2_RXDMAEN BIT(SPI_CR2_RXDMAEN_BIT) + +/* Status register */ + +#define SPI_SR_BSY_BIT 7 +#define SPI_SR_OVR_BIT 6 +#define SPI_SR_MODF_BIT 5 +#define SPI_SR_CRCERR_BIT 4 +#define SPI_SR_UDR_BIT 3 +#define SPI_SR_CHSIDE_BIT 2 +#define SPI_SR_TXE_BIT 1 +#define SPI_SR_RXNE_BIT 0 + +#define SPI_SR_BSY BIT(SPI_SR_BSY_BIT) +#define SPI_SR_OVR BIT(SPI_SR_OVR_BIT) +#define SPI_SR_MODF BIT(SPI_SR_MODF_BIT) +#define SPI_SR_CRCERR BIT(SPI_SR_CRCERR_BIT) +#define SPI_SR_UDR BIT(SPI_SR_UDR_BIT) +#define SPI_SR_CHSIDE BIT(SPI_SR_CHSIDE_BIT) +#define SPI_SR_CHSIDE_LEFT (0x0 << SPI_SR_CHSIDE_BIT) +#define SPI_SR_CHSIDE_RIGHT (0x1 << SPI_SR_CHSIDE_BIT) +#define SPI_SR_TXE BIT(SPI_SR_TXE_BIT) +#define SPI_SR_RXNE BIT(SPI_SR_RXNE_BIT) + +/* I2S configuration register */ + +/* RM0008-ism: CR1 has "CPOL", I2SCFGR has "CKPOL". */ + +#define SPI_I2SCFGR_I2SMOD_BIT 11 +#define SPI_I2SCFGR_I2SE_BIT 10 +#define SPI_I2SCFGR_PCMSYNC_BIT 7 +#define SPI_I2SCFGR_CKPOL_BIT 3 +#define SPI_I2SCFGR_CHLEN_BIT 0 + +#define SPI_I2SCFGR_I2SMOD BIT(SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SMOD_SPI (0x0 << SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SMOD_I2S (0x1 << SPI_I2SCFGR_I2SMOD_BIT) +#define SPI_I2SCFGR_I2SE BIT(SPI_I2SCFGR_I2SE_BIT) +#define SPI_I2SCFGR_I2SCFG (0x3 << 8) +#define SPI_I2SCFGR_I2SCFG_SLAVE_TX (0x0 << 8) +#define SPI_I2SCFGR_I2SCFG_SLAVE_RX (0x1 << 8) +#define SPI_I2SCFGR_I2SCFG_MASTER_TX (0x2 << 8) +#define SPI_I2SCFGR_I2SCFG_MASTER_RX (0x3 << 8) +#define SPI_I2SCFGR_PCMSYNC BIT(SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_PCMSYNC_SHORT (0x0 << SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_PCMSYNC_LONG (0x1 << SPI_I2SCFGR_PCMSYNC_BIT) +#define SPI_I2SCFGR_I2SSTD (0x3 << 4) +#define SPI_I2SCFGR_I2SSTD_PHILLIPS (0x0 << 4) +#define SPI_I2SCFGR_I2SSTD_MSB (0x1 << 4) +#define SPI_I2SCFGR_I2SSTD_LSB (0x2 << 4) +#define SPI_I2SCFGR_I2SSTD_PCM (0x3 << 4) +#define SPI_I2SCFGR_CKPOL BIT(SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_CKPOL_LOW (0x0 << SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_CKPOL_HIGH (0x1 << SPI_I2SCFGR_CKPOL_BIT) +#define SPI_I2SCFGR_DATLEN (0x3 << 1) +#define SPI_I2SCFGR_DATLEN_16_BIT (0x0 << 1) +#define SPI_I2SCFGR_DATLEN_24_BIT (0x1 << 1) +#define SPI_I2SCFGR_DATLEN_32_BIT (0x2 << 1) +#define SPI_I2SCFGR_CHLEN BIT(SPI_I2SCFGR_CHLEN_BIT) +#define SPI_I2SCFGR_CHLEN_16_BIT (0x0 << SPI_I2SCFGR_CHLEN_BIT) +#define SPI_I2SCFGR_CHLEN_32_BIT (0x1 << SPI_I2SCFGR_CHLEN_BIT) + +/* + * Devices + */ + +/** SPI device type */ +typedef struct spi_dev { + spi_reg_map *regs; + rcc_clk_id clk_id; + nvic_irq_num irq_num; +} spi_dev; + +/** SPI device 1 */ +extern spi_dev *SPI1; +/** SPI device 2 */ +extern spi_dev *SPI2; +#ifdef STM32_HIGH_DENSITY +/** SPI device 3 */ +extern spi_dev *SPI3; +#endif + +/* + * SPI Convenience functions + */ + +void spi_init(spi_dev *dev); + +void spi_master_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit); + +void spi_slave_gpio_cfg(gpio_dev *nss_dev, + gpio_dev *comm_dev, + uint8 nss_bit, + uint8 sck_bit, + uint8 miso_bit, + uint8 mosi_bit); + +/** + * @brief SPI mode configuration. + * + * Determines a combination of clock polarity (CPOL), which determines + * idle state of the clock line, and clock phase (CPHA), which + * determines which clock edge triggers data capture. + */ +typedef enum { + SPI_MODE_0, /**< Clock line idles low (0), data capture on first + clock transition. */ + SPI_MODE_1, /**< Clock line idles low (0), data capture on second + clock transition */ + SPI_MODE_2, /**< Clock line idles high (1), data capture on first + clock transition. */ + SPI_MODE_3 /**< Clock line idles high (1), data capture on + second clock transition. */ +} spi_mode; + +/** + * @brief SPI baud rate configuration, as a divisor of f_PCLK, the + * PCLK clock frequency. + */ +typedef enum { + SPI_BAUD_PCLK_DIV_2 = SPI_CR1_BR_PCLK_DIV_2, /**< f_PCLK/2 */ + SPI_BAUD_PCLK_DIV_4 = SPI_CR1_BR_PCLK_DIV_4, /**< f_PCLK/4 */ + SPI_BAUD_PCLK_DIV_8 = SPI_CR1_BR_PCLK_DIV_8, /**< f_PCLK/8 */ + SPI_BAUD_PCLK_DIV_16 = SPI_CR1_BR_PCLK_DIV_16, /**< f_PCLK/16 */ + SPI_BAUD_PCLK_DIV_32 = SPI_CR1_BR_PCLK_DIV_32, /**< f_PCLK/32 */ + SPI_BAUD_PCLK_DIV_64 = SPI_CR1_BR_PCLK_DIV_64, /**< f_PCLK/64 */ + SPI_BAUD_PCLK_DIV_128 = SPI_CR1_BR_PCLK_DIV_128, /**< f_PCLK/128 */ + SPI_BAUD_PCLK_DIV_256 = SPI_CR1_BR_PCLK_DIV_256, /**< f_PCLK/256 */ +} spi_baud_rate; + +/** + * @brief SPI initialization flags. + * @see spi_master_enable() + * @see spi_slave_enable() + */ +typedef enum { + SPI_BIDIMODE = SPI_CR1_BIDIMODE, /**< Bidirectional mode enable */ + SPI_BIDIOE = SPI_CR1_BIDIOE, /**< Output enable in bidirectional + mode */ + SPI_CRCEN = SPI_CR1_CRCEN, /**< Cyclic redundancy check (CRC) + enable */ + SPI_DFF_8_BIT = SPI_CR1_DFF_8_BIT, /**< 8-bit data frame format (this is + the default) */ + SPI_DFF_16_BIT = SPI_CR1_DFF_16_BIT, /**< 16-bit data frame format */ + SPI_RX_ONLY = SPI_CR1_RXONLY, /**< Receive only */ + SPI_SW_SLAVE = SPI_CR1_SSM, /**< Software slave management */ + SPI_SOFT_SS = SPI_CR1_SSI, /**< Software (internal) slave + select. This flag only has an + effect when used in combination + with SPI_SW_SLAVE. */ + SPI_FRAME_LSB = SPI_CR1_LSBFIRST, /**< LSB-first (little-endian) frame + format */ + SPI_FRAME_MSB = 0, /**< MSB-first (big-endian) frame + format (this is the default) */ +} spi_cfg_flag; + +void spi_master_enable(spi_dev *dev, + spi_baud_rate baud, + spi_mode mode, + uint32 flags); + +void spi_slave_enable(spi_dev *dev, + spi_mode mode, + uint32 flags); + +uint32 spi_tx(spi_dev *dev, const void *buf, uint32 len); + +void spi_foreach(void (*fn)(spi_dev (*dev))); + +void spi_peripheral_enable(spi_dev *dev); +void spi_peripheral_disable(spi_dev *dev); + +void spi_tx_dma_enable(spi_dev *dev); +void spi_tx_dma_disable(spi_dev *dev); + +void spi_rx_dma_enable(spi_dev *dev); +void spi_rx_dma_disable(spi_dev *dev); + +/** + * @brief Determine if a SPI peripheral is enabled. + * @parm dev SPI device + * @return True, if and only if dev's peripheral is enabled. + */ +static inline uint8 spi_is_enabled(spi_dev *dev) { + return dev->regs->CR1 & SPI_CR1_SPE_BIT; +} + +/** + * @brief Disable all SPI peripherals + */ +static inline void spi_peripheral_disable_all(void) { + spi_foreach(spi_peripheral_disable); +} + +/** Available SPI interrupts */ +typedef enum { + SPI_TXE_INTERRUPT = SPI_CR2_TXEIE, /**< TX buffer empty interrupt */ + SPI_RXNE_INTERRUPT = SPI_CR2_RXNEIE, /**< RX buffer not empty interrupt */ + SPI_ERR_INTERRUPT = SPI_CR2_ERRIE /**< + * Error interrupt (CRC, overrun, + * and mode fault errors for SPI; + * underrun, overrun errors for I2S) + */ +} spi_interrupt; + +/** + * @brief Mask for all spi_interrupt values + * @see spi_interrupt + */ +#define SPI_INTERRUPTS_ALL (SPI_TXE_INTERRUPT | \ + SPI_RXNE_INTERRUPT | \ + SPI_ERR_INTERRUPT) + +/** + * @brief Enable SPI interrupt requests + * @param dev SPI device + * @param interrupt_flags Bitwise OR of spi_interrupt values to enable + * @see spi_interrupt + */ +static inline void spi_irq_enable(spi_dev *dev, uint32 interrupt_flags) { + dev->regs->CR2 |= interrupt_flags; + nvic_irq_enable(dev->irq_num); +} + +/** + * @brief Disable SPI interrupt requests + * @param dev SPI device + * @param interrupt_flags Bitwise OR of spi_interrupt values to disable + * @see spi_interrupt + */ +static inline void spi_irq_disable(spi_dev *dev, uint32 interrupt_flags) { + dev->regs->CR2 &= ~interrupt_flags; +} + +/** + * @brief Get the data frame format flags with which a SPI port is + * configured. + * @param dev SPI device whose data frame format to get. + * @return SPI_DFF_8_BIT, if dev has an 8-bit data frame format. + * Otherwise, SPI_DFF_16_BIT. + */ +static inline spi_cfg_flag spi_dff(spi_dev *dev) { + return ((dev->regs->CR1 & SPI_CR1_DFF) == SPI_CR1_DFF_8_BIT ? + SPI_DFF_8_BIT : + SPI_DFF_16_BIT); +} + +/** + * @brief Determine whether the device's peripheral receive (RX) + * register is empty. + * @param dev SPI device + * @return true, iff dev's RX register is empty. + */ +static inline uint8 spi_is_rx_nonempty(spi_dev *dev) { + return dev->regs->SR & SPI_SR_RXNE; +} + +/** + * @brief Retrieve the contents of the device's peripheral receive + * (RX) register. + * + * You may only call this function when the RX register is nonempty. + * Calling this function clears the contents of the RX register. + * + * @param dev SPI device + * @return Contents of dev's peripheral RX register + * @see spi_is_rx_reg_nonempty() + */ +static inline uint16 spi_rx_reg(spi_dev *dev) { + return (uint16)dev->regs->DR; +} + +/** + * @brief Determine whether the device's peripheral transmit (TX) + * register is empty. + * @param dev SPI device + * @return true, iff dev's TX register is empty. + */ +static inline uint8 spi_is_tx_empty(spi_dev *dev) { + return dev->regs->SR & SPI_SR_TXE; +} + +/** + * @brief Load a value into the device's peripheral transmit (TX) register. + * + * You may only call this function when the TX register is empty. + * Calling this function loads val into the peripheral's TX register. + * If the device is properly configured, this will initiate a + * transmission, the completion of which will cause the TX register to + * be empty again. + * + * @param dev SPI device + * @param val Value to load into the TX register. If the SPI data + * frame format is 8 bit, the value must be right-aligned. + * @see spi_is_tx_reg_empty() + * @see spi_init() + * @see spi_master_enable() + * @see spi_slave_enable() + */ +static inline void spi_tx_reg(spi_dev *dev, uint16 val) { + dev->regs->DR = val; +} + +/** + * @brief Determine whether the device's peripheral busy (SPI_SR_BSY) + * flag is set. + * @param dev SPI device + * @return true, iff dev's BSY flag is set. + */ +static inline uint8 spi_is_busy(spi_dev *dev) { + return dev->regs->SR & SPI_SR_BSY; } +/* + * I2S convenience functions (TODO) + */ + #ifdef __cplusplus } #endif #endif - diff --git a/wirish/boards/maple.h b/wirish/boards/maple.h index 4a4465c..4b55f7a 100644 --- a/wirish/boards/maple.h +++ b/wirish/boards/maple.h @@ -52,6 +52,19 @@ #define BOARD_USART3_TX_PIN 29 #define BOARD_USART3_RX_PIN 30 +/* Number of SPI ports */ +#define BOARD_NR_SPI 2 + +/* Default SPI pin numbers (not considering AFIO remap) */ +#define BOARD_SPI1_NSS_PIN 10 +#define BOARD_SPI1_MOSI_PIN 11 +#define BOARD_SPI1_MISO_PIN 12 +#define BOARD_SPI1_SCK_PIN 13 +#define BOARD_SPI2_NSS_PIN 31 +#define BOARD_SPI2_MOSI_PIN 34 +#define BOARD_SPI2_MISO_PIN 33 +#define BOARD_SPI2_SCK_PIN 32 + /* Total number of GPIO pins that are broken out to headers and * intended for general use. */ #define BOARD_NR_GPIO_PINS 44 diff --git a/wirish/boards/maple_RET6.h b/wirish/boards/maple_RET6.h index 97e609d..4195857 100644 --- a/wirish/boards/maple_RET6.h +++ b/wirish/boards/maple_RET6.h @@ -55,6 +55,16 @@ #define BOARD_USART3_TX_PIN 29 #define BOARD_USART3_RX_PIN 30 +#define BOARD_NR_SPI 2 +#define BOARD_SPI1_NSS_PIN 10 +#define BOARD_SPI1_MOSI_PIN 11 +#define BOARD_SPI1_MISO_PIN 12 +#define BOARD_SPI1_SCK_PIN 13 +#define BOARD_SPI2_NSS_PIN 31 +#define BOARD_SPI2_MOSI_PIN 34 +#define BOARD_SPI2_MISO_PIN 33 +#define BOARD_SPI2_SCK_PIN 32 + #define BOARD_NR_GPIO_PINS 44 #define BOARD_NR_PWM_PINS 16 #define BOARD_NR_ADC_PINS 15 diff --git a/wirish/boards/maple_mini.h b/wirish/boards/maple_mini.h index 3df1da8..fde7f98 100644 --- a/wirish/boards/maple_mini.h +++ b/wirish/boards/maple_mini.h @@ -54,6 +54,16 @@ #define BOARD_USART3_TX_PIN 1 #define BOARD_USART3_RX_PIN 0 +#define BOARD_NR_SPI 2 +#define BOARD_SPI1_NSS_PIN 7 +#define BOARD_SPI1_MOSI_PIN 4 +#define BOARD_SPI1_MISO_PIN 5 +#define BOARD_SPI1_SCK_PIN 6 +#define BOARD_SPI2_NSS_PIN 31 +#define BOARD_SPI2_MOSI_PIN 28 +#define BOARD_SPI2_MISO_PIN 29 +#define BOARD_SPI2_SCK_PIN 30 + #define BOARD_NR_GPIO_PINS 34 #define BOARD_NR_PWM_PINS 12 #define BOARD_NR_ADC_PINS 10 diff --git a/wirish/boards/maple_native.cpp b/wirish/boards/maple_native.cpp index ab8b282..c1f8d5c 100644 --- a/wirish/boards/maple_native.cpp +++ b/wirish/boards/maple_native.cpp @@ -171,7 +171,7 @@ extern const uint8 boardADCPins[BOARD_NR_ADC_PINS] __FLASH__ = { 54, 55 }; -/* FIXME! see comment by BOARD_NR_USED_PINS in maple_native.h */ +/* FIXME [0.0.10] see comment by BOARD_NR_USED_PINS in maple_native.h */ extern const uint8 boardUsedPins[BOARD_NR_USED_PINS] __FLASH__ = { BOARD_LED_PIN, BOARD_BUTTON_PIN, BOARD_JTMS_SWDIO_PIN, BOARD_JTCK_SWCLK_PIN, BOARD_JTDI_PIN, BOARD_JTDO_PIN, BOARD_NJTRST_PIN diff --git a/wirish/boards/maple_native.h b/wirish/boards/maple_native.h index 13df153..2cbd406 100644 --- a/wirish/boards/maple_native.h +++ b/wirish/boards/maple_native.h @@ -58,12 +58,27 @@ #define BOARD_UART5_TX_PIN 21 #define BOARD_UART5_RX_PIN 29 +#define BOARD_NR_SPI 3 +#define BOARD_SPI1_NSS_PIN 52 +#define BOARD_SPI1_MOSI_PIN 55 +#define BOARD_SPI1_MISO_PIN 54 +#define BOARD_SPI1_SCK_PIN 53 +#define BOARD_SPI2_NSS_PIN 2 +#define BOARD_SPI2_MOSI_PIN 5 +#define BOARD_SPI2_MISO_PIN 4 +#define BOARD_SPI2_SCK_PIN 3 +#define BOARD_SPI3_NSS_PIN 103 +#define BOARD_SPI3_MOSI_PIN 37 +#define BOARD_SPI3_MISO_PIN 105 +#define BOARD_SPI3_SCK_PIN 104 + #define BOARD_NR_GPIO_PINS 106 #define BOARD_NR_PWM_PINS 18 #define BOARD_NR_ADC_PINS 21 -/* FIXME! this isn't true at all; almost all of the triple header pins - * are used by the FSMC by default. Fix this (and the corresponding - * boardUsedPins definition in maple_native.cpp) by QA time. */ +/* FIXME [0.0.10] this isn't true at all; almost all of the triple + * header pins are used by the FSMC by default. Fix this (and the + * corresponding boardUsedPins definition in maple_native.cpp) by QA + * time. */ #define BOARD_NR_USED_PINS 7 #define BOARD_JTMS_SWDIO_PIN 101 diff --git a/wirish/comm/HardwareSPI.cpp b/wirish/comm/HardwareSPI.cpp index aea7734..1b36d21 100644 --- a/wirish/comm/HardwareSPI.cpp +++ b/wirish/comm/HardwareSPI.cpp @@ -23,120 +23,283 @@ *****************************************************************************/ /** - * @brief HardwareSPI "wiring-like" api for SPI + * @author Marti Bolivar <mbolivar@leaflabs.com> + * @brief Wirish SPI implementation. */ -/* NOTES: - * - * Speeds: - * ----------------------------------- - * Interface num: SPI1 SPI2 - * Bus APB2 APB1 - * ----------------------------------- - * Prescaler Frequencies - * ----------------------------------- - * 2: N/A 18 000 000 - * 4: 18 000 000 9 000 000 - * 8: 9 000 000 4 500 000 - * 16: 4 500 000 2 250 000 - * 32: 2 250 000 1 125 000 - * 64: 1 125 000 562 500 - * 128: 562 500 281 250 - * 256: 281 250 140 625 - * - * TODO: Do the complementary PWM outputs mess up SPI2? - * */ +#include "HardwareSPI.h" -#include "spi.h" #include "timer.h" +#include "util.h" #include "wirish.h" -#include "HardwareSPI.h" +#include "boards.h" -static const uint32 prescaleFactors[MAX_SPI_FREQS] = { - SPI_PRESCALE_2, // SPI_18MHZ - SPI_PRESCALE_4, // SPI_9MHZ - SPI_PRESCALE_8, // SPI_4_5MHZ - SPI_PRESCALE_16, // SPI_2_25MHZ - SPI_PRESCALE_32, // SPI_1_125MHZ - SPI_PRESCALE_64, // SPI_562_500KHZ - SPI_PRESCALE_128, // SPI_281_250KHZ - SPI_PRESCALE_256, // SPI_140_625KHZ -}; +static void enable_device(spi_dev *dev, + bool as_master, + SPIFrequency frequency, + spi_cfg_flag endianness, + spi_mode mode); -/** - * @brief Initialize a SPI peripheral - * @param freq frequency to run at, must one of the following values: - * - SPI_18MHZ - * - SPI_9MHZ - * - SPI_4_5MHZ - * - SPI_2_25MHZ - * - SPI_1_125MHZ - * - SPI_562_500KHZ - * - SPI_281_250KHZ - * - SPI_140_625KHZ - * @param endianness endianness of the data frame, must be either LSBFIRST - * or MSBFIRST - * @param mode SPI standard CPOL and CPHA levels +/* + * Constructor, public methods */ -void HardwareSPI::begin(SPIFrequency freq, uint32 endianness, uint32 mode) { - uint32 spi_num = this->spi_num; - uint32 prescale; - - if ((freq >= MAX_SPI_FREQS) || - !((endianness == LSBFIRST) || - (endianness == MSBFIRST)) || - (mode >= 4)) { + +HardwareSPI::HardwareSPI(uint32 spi_num) { + switch (spi_num) { + case 1: + this->spi_d = SPI1; + break; + case 2: + this->spi_d = SPI2; + break; +#ifdef STM32_HIGH_DENSITY + case 3: + this->spi_d = SPI3; + break; +#endif + default: + ASSERT(0); + } +} + +void HardwareSPI::begin(SPIFrequency frequency, uint32 bitOrder, uint32 mode) { + if (mode >= 4) { + ASSERT(0); return; } + spi_cfg_flag end = bitOrder == MSBFIRST ? SPI_FRAME_MSB : SPI_FRAME_LSB; + spi_mode m = (spi_mode)mode; + enable_device(this->spi_d, true, frequency, end, m); +} - if (spi_num == 1) { - /* SPI1 is too fast for 140625 */ - if (freq == SPI_140_625KHZ) { - return; - } +void HardwareSPI::begin(void) { + this->begin(SPI_1_125MHZ, MSBFIRST, 0); +} - /* Turn off PWM on shared pins */ - timer_set_mode(TIMER3, 2, TIMER_DISABLED); - timer_set_mode(TIMER3, 1, TIMER_DISABLED); +void HardwareSPI::beginSlave(uint32 bitOrder, uint32 mode) { + if (mode >= 4) { + ASSERT(0); + return; } + spi_cfg_flag end = bitOrder == MSBFIRST ? SPI_FRAME_MSB : SPI_FRAME_LSB; + spi_mode m = (spi_mode)mode; + enable_device(this->spi_d, false, (SPIFrequency)0, end, m); +} - endianness = (endianness == LSBFIRST) ? SPI_LSBFIRST : SPI_MSBFIRST; - prescale = (spi_num == 1) ? - prescaleFactors[freq + 1] : - prescaleFactors[freq]; +void HardwareSPI::beginSlave(void) { + this->beginSlave(MSBFIRST, 0); +} - spi_init(spi_num, prescale, endianness, mode); +void HardwareSPI::end(void) { + if (!spi_is_enabled(this->spi_d)) { + return; + } + + // Follows RM0008's sequence for disabling a SPI in master/slave + // full duplex mode. + while (spi_is_rx_nonempty(this->spi_d)) { + // FIXME [0.1.0] remove this once you have an interrupt based driver + volatile uint16 rx __attribute__((unused)) = spi_rx_reg(this->spi_d); + } + while (!spi_is_tx_empty(this->spi_d)) + ; + while (spi_is_busy(this->spi_d)) + ; + spi_peripheral_disable(this->spi_d); } -/** - * @brief Initialize a SPI peripheral with a default speed of 1.125 - * MHZ, MSBFIRST, mode 0 - */ -void HardwareSPI::begin(void) { - begin(SPI_1_125MHZ, MSBFIRST, 0); +uint8 HardwareSPI::read(void) { + uint8 buf[1]; + this->read(buf, 1); + return buf[0]; } -/** - * @brief send a byte out the spi peripheral - * @param data byte to send +void HardwareSPI::read(uint8 *buf, uint32 len) { + uint32 rxed = 0; + while (rxed < len) { + while (!spi_is_rx_nonempty(this->spi_d)) + ; + buf[rxed++] = (uint8)spi_rx_reg(this->spi_d); + } +} + +void HardwareSPI::write(uint8 byte) { + uint8 buf[] = {byte}; + this->write(buf, 1); +} + +void HardwareSPI::write(uint8 *data, uint32 length) { + uint32 txed = 0; + while (txed < length) { + txed += spi_tx(this->spi_d, data + txed, length - txed); + } +} + +uint8 HardwareSPI::transfer(uint8 byte) { + this->write(byte); + return this->read(); +} + +/* + * Deprecated functions */ + uint8 HardwareSPI::send(uint8 data) { - return spi_tx_byte(this->spi_num, data); + uint8 buf[] = {data}; + return this->send(buf, 1); } uint8 HardwareSPI::send(uint8 *buf, uint32 len) { - return spi_tx(this->spi_num, buf, len); + if (len == 0) { + ASSERT(0); + return 0; + } + uint32 txed = 0; + uint8 ret = 0; // shut up, GCC + while (txed < len) { + this->write(buf[txed++]); + ret = this->read(); + } + return ret; } -/** - * @brief read a byte from the spi peripheral - * @return byte in the buffer - */ uint8 HardwareSPI::recv(void) { - return spi_rx(this->spi_num); + return this->read(); } -HardwareSPI::HardwareSPI(uint32 spi_num) { - this->spi_num = spi_num; +/* + * Auxiliary functions + */ + +static void configure_gpios(spi_dev *dev, bool as_master); +static spi_baud_rate determine_baud_rate(spi_dev *dev, SPIFrequency freq); + +/* Enables the device in master or slave full duplex mode. If you + * change this code, you must ensure that appropriate changes are made + * to HardwareSPI::end(). */ +static void enable_device(spi_dev *dev, + bool as_master, + SPIFrequency freq, + spi_cfg_flag endianness, + spi_mode mode) { + if (freq >= MAX_SPI_FREQS) { + ASSERT(0); + return; + } + + spi_baud_rate baud = determine_baud_rate(dev, freq); + uint32 cfg_flags = (endianness | SPI_DFF_8_BIT | SPI_SW_SLAVE | + (as_master ? SPI_SOFT_SS : 0)); + + spi_init(dev); + configure_gpios(dev, as_master); + if (as_master) { + spi_master_enable(dev, baud, mode, cfg_flags); + } else { + spi_slave_enable(dev, mode, cfg_flags); + } +} + +static void disable_pwm(const stm32_pin_info *i) { + if (i->timer_device) { + timer_set_mode(i->timer_device, i->timer_channel, TIMER_DISABLED); + } +} + +typedef struct spi_pins { + uint8 nss; + uint8 sck; + uint8 miso; + uint8 mosi; +} spi_pins; + +static void configure_gpios(spi_dev *dev, bool as_master) { + const spi_pins spi_pin_config[] = { + {BOARD_SPI1_NSS_PIN, + BOARD_SPI1_SCK_PIN, + BOARD_SPI1_MISO_PIN, + BOARD_SPI1_MOSI_PIN}, + {BOARD_SPI2_NSS_PIN, + BOARD_SPI2_SCK_PIN, + BOARD_SPI2_MISO_PIN, + BOARD_SPI2_MOSI_PIN}, +#ifdef STM32_HIGH_DENSITY + {BOARD_SPI3_NSS_PIN, + BOARD_SPI3_SCK_PIN, + BOARD_SPI3_MISO_PIN, + BOARD_SPI3_MOSI_PIN}, +#endif + }; + + const spi_pins *pins; + + switch (dev->clk_id) { + case RCC_SPI1: + pins = &spi_pin_config[0]; + break; + case RCC_SPI2: + pins = &spi_pin_config[1]; + break; +#ifdef STM32_HIGH_DENSITY + case RCC_SPI3: + pins = &spi_pin_config[2]; + break; +#endif + default: + ASSERT(0); + return; + } + + const stm32_pin_info *nssi = &PIN_MAP[pins->nss]; + const stm32_pin_info *scki = &PIN_MAP[pins->sck]; + const stm32_pin_info *misoi = &PIN_MAP[pins->miso]; + const stm32_pin_info *mosii = &PIN_MAP[pins->mosi]; + + disable_pwm(nssi); + disable_pwm(scki); + disable_pwm(misoi); + disable_pwm(mosii); + + if (as_master) { + spi_master_gpio_cfg(nssi->gpio_device, + scki->gpio_device, + nssi->gpio_bit, + scki->gpio_bit, + misoi->gpio_bit, + mosii->gpio_bit); + } else { + spi_slave_gpio_cfg(nssi->gpio_device, + scki->gpio_device, + nssi->gpio_bit, + scki->gpio_bit, + misoi->gpio_bit, + mosii->gpio_bit); + } +} + +static const spi_baud_rate baud_rates[MAX_SPI_FREQS] __FLASH__ = { + SPI_BAUD_PCLK_DIV_2, + SPI_BAUD_PCLK_DIV_4, + SPI_BAUD_PCLK_DIV_8, + SPI_BAUD_PCLK_DIV_16, + SPI_BAUD_PCLK_DIV_32, + SPI_BAUD_PCLK_DIV_64, + SPI_BAUD_PCLK_DIV_128, + SPI_BAUD_PCLK_DIV_256, +}; + +/* + * Note: This assumes you're on a LeafLabs-style board + * (CYCLES_PER_MICROSECOND == 72, APB2 at 72MHz, APB1 at 36MHz). + */ +static spi_baud_rate determine_baud_rate(spi_dev *dev, SPIFrequency freq) { + if (rcc_dev_clk(dev->clk_id) == RCC_APB2 && freq == SPI_140_625KHZ) { + /* APB2 peripherals are too fast for 140.625 KHz */ + ASSERT(0); + return (spi_baud_rate)~0; + } + return (rcc_dev_clk(dev->clk_id) == RCC_APB2 ? + baud_rates[freq + 1] : + baud_rates[freq]); } diff --git a/wirish/comm/HardwareSPI.h b/wirish/comm/HardwareSPI.h index 7241d0b..1b2a966 100644 --- a/wirish/comm/HardwareSPI.h +++ b/wirish/comm/HardwareSPI.h @@ -3,60 +3,188 @@ * * Copyright (c) 2010 Perry Hung. * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. *****************************************************************************/ /** - * @brief HardwareSPI definitions + * @file HardwareSPI.h + * @brief High-level SPI interface + * + * This is a "bare essentials" polling driver for now. */ +/* TODO [0.1.0] Remove deprecated methods. */ + +#include "libmaple_types.h" +#include "spi.h" + +#include "boards.h" + #ifndef _HARDWARESPI_H_ #define _HARDWARESPI_H_ /** - * Defines the possible SPI communication speeds. + * @brief Defines the possible SPI communication speeds. */ typedef enum SPIFrequency { SPI_18MHZ = 0, /**< 18 MHz */ SPI_9MHZ = 1, /**< 9 MHz */ SPI_4_5MHZ = 2, /**< 4.5 MHz */ - SPI_2_25MHZ = 3, /**< 2.25 MHZ */ + SPI_2_25MHZ = 3, /**< 2.25 MHz */ SPI_1_125MHZ = 4, /**< 1.125 MHz */ SPI_562_500KHZ = 5, /**< 562.500 KHz */ SPI_281_250KHZ = 6, /**< 281.250 KHz */ SPI_140_625KHZ = 7, /**< 140.625 KHz */ - MAX_SPI_FREQS = 8, /**< The number of SPI frequencies. */ } SPIFrequency; -/* Documented by hand in docs/source/lang/api/hardwarespi.rst; if you - make any changes, make sure to update this document. */ +#define MAX_SPI_FREQS 8 + +#if CYCLES_PER_MICROSECOND != 72 +/* TODO [0.2.0?] something smarter than this */ +#warn "Unexpected clock speed; SPI frequency calculation will be incorrect" +#endif + +/** + * @brief Wirish SPI interface. + * + * This implementation uses software slave management, so the caller + * is responsible for controlling the slave select line. + */ class HardwareSPI { - private: - uint32 spi_num; +public: + /** + * @param spiPortNumber Number of the SPI port to manage. + */ + HardwareSPI(uint32 spiPortNumber); + + /** + * @brief Turn on a SPI port and set its GPIO pin modes for use as master. + * + * SPI port is enabled in full duplex mode, with software slave management. + * + * @param frequency Communication frequency + * @param bitOrder Either LSBFIRST (little-endian) or MSBFIRST (big-endian) + * @param mode SPI mode to use, one of SPI_MODE_0, SPI_MODE_1, + * SPI_MODE_2, and SPI_MODE_3. + */ + void begin(SPIFrequency frequency, uint32 bitOrder, uint32 mode); - public: - HardwareSPI(uint32 spi_num); + /** + * @brief Equivalent to begin(SPI_1_125MHZ, MSBFIRST, 0). + */ void begin(void); - void begin(SPIFrequency freq, uint32 endianness, uint32 mode); + + /** + * @brief Turn on a SPI port and set its GPIO pin modes for use as a slave. + * + * SPI port is enabled in full duplex mode, with software slave management. + * + * @param bitOrder Either LSBFIRST (little-endian) or MSBFIRST(big-endian) + * @param mode SPI mode to use + */ + void beginSlave(uint32 bitOrder, uint32 mode); + + /** + * @brief Equivalent to beginSlave(MSBFIRST, 0). + */ + void beginSlave(void); + + /** + * @brief Disables the SPI port, but leaves its GPIO pin modes unchanged. + */ + void end(void); + + /** + * @brief Return the next unread byte. + * + * If there is no unread byte waiting, this function will block + * until one is received. + */ + uint8 read(void); + + /** + * @brief Read length bytes, storing them into buffer. + * @param buffer Buffer to store received bytes into. + * @param length Number of bytes to store in buffer. This + * function will block until the desired number of + * bytes have been read. + */ + void read(uint8 *buffer, uint32 length); + + /** + * @brief Transmit a byte. + * @param data Byte to transmit. + */ + void write(uint8 data); + + /** + * @brief Transmit multiple bytes. + * @param buffer Bytes to transmit. + * @param length Number of bytes in buffer to transmit. + */ + void write(uint8 *buffer, uint32 length); + + /** + * @brief Transmit a byte, then return the next unread byte. + * + * This function transmits before receiving. + * + * @param data Byte to transmit. + * @return Next unread byte. + */ + uint8 transfer(uint8 data); + + /* -- The following methods are deprecated --------------------------- */ + + /** + * @brief Deprecated. + * + * Use HardwareSPI::transfer() instead. + * + * @see HardwareSPI::transfer() + */ uint8 send(uint8 data); + + /** + * @brief Deprecated. + * + * Use HardwareSPI::write() in combination with + * HardwareSPI::read() (or HardwareSPI::transfer()) instead. + * + * @see HardwareSPI::write() + * @see HardwareSPI::read() + * @see HardwareSPI::transfer() + */ uint8 send(uint8 *data, uint32 length); + + /** + * @brief Deprecated. + * + * Use HardwareSPI::read() instead. + * + * @see HardwareSPI::read() + */ uint8 recv(void); +private: + spi_dev *spi_d; }; #endif |