aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--docs/source/lang/api/hardwarespi.rst215
-rw-r--r--docs/source/spi.rst6
-rw-r--r--examples/test-spi-roundtrip.cpp197
-rw-r--r--libmaple/spi.c333
-rw-r--r--libmaple/spi.h495
-rw-r--r--wirish/boards/maple.h13
-rw-r--r--wirish/boards/maple_RET6.h10
-rw-r--r--wirish/boards/maple_mini.h10
-rw-r--r--wirish/boards/maple_native.cpp2
-rw-r--r--wirish/boards/maple_native.h21
-rw-r--r--wirish/comm/HardwareSPI.cpp335
-rw-r--r--wirish/comm/HardwareSPI.h178
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