From fbba6c38514ae0ea395d96b444af048b9e1a1a91 Mon Sep 17 00:00:00 2001
From: marble <markusblechschmidt@gmail.com>
Date: Wed, 10 May 2017 23:06:43 +0200
Subject: [PATCH] driver/soft_spi: initial implementation

---
 drivers/Makefile.dep                       |   5 +
 drivers/Makefile.include                   |   3 +
 drivers/include/soft_spi.h                 | 265 +++++++++++++++++++++
 drivers/soft_spi/Makefile                  |   1 +
 drivers/soft_spi/include/soft_spi_params.h |  60 +++++
 drivers/soft_spi/soft_spi.c                | 211 ++++++++++++++++
 tests/driver_soft_spi/Makefile             |  16 ++
 tests/driver_soft_spi/README.md            |  12 +
 tests/driver_soft_spi/main.c               |  71 ++++++
 9 files changed, 644 insertions(+)
 create mode 100644 drivers/include/soft_spi.h
 create mode 100644 drivers/soft_spi/Makefile
 create mode 100644 drivers/soft_spi/include/soft_spi_params.h
 create mode 100644 drivers/soft_spi/soft_spi.c
 create mode 100644 tests/driver_soft_spi/Makefile
 create mode 100644 tests/driver_soft_spi/README.md
 create mode 100644 tests/driver_soft_spi/main.c

diff --git a/drivers/Makefile.dep b/drivers/Makefile.dep
index cb9998314c..05fcf57a2f 100644
--- a/drivers/Makefile.dep
+++ b/drivers/Makefile.dep
@@ -214,6 +214,11 @@ ifneq (,$(filter sdcard_spi,$(USEMODULE)))
   USEMODULE += xtimer
 endif
 
+ifneq (,$(filter soft_spi,$(USEMODULE)))
+  FEATURES_REQUIRED += periph_gpio
+  USEMODULE += xtimer
+endif
+
 ifneq (,$(filter sht11,$(USEMODULE)))
   USEMODULE += xtimer
 endif
diff --git a/drivers/Makefile.include b/drivers/Makefile.include
index 9ddc76198f..b6cd4e40af 100644
--- a/drivers/Makefile.include
+++ b/drivers/Makefile.include
@@ -115,6 +115,9 @@ endif
 ifneq (,$(filter sdcard_spi,$(USEMODULE)))
   USEMODULE_INCLUDES += $(RIOTBASE)/drivers/sdcard_spi/include
 endif
+ifneq (,$(filter soft_spi,$(USEMODULE)))
+    USEMODULE_INCLUDES += $(RIOTBASE)/drivers/soft_spi/include
+endif
 ifneq (,$(filter veml6070,$(USEMODULE)))
   USEMODULE_INCLUDES += $(RIOTBASE)/drivers/veml6070/include
 endif
diff --git a/drivers/include/soft_spi.h b/drivers/include/soft_spi.h
new file mode 100644
index 0000000000..d2160647ab
--- /dev/null
+++ b/drivers/include/soft_spi.h
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2017 Hamburg University of Applied Sciences
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    driver_soft_spi Soft SPI
+ * @ingroup     drivers
+ * @brief       Software implemented Serial Peripheral Interface bus
+ * This module provides a software implemented Serial Peripheral Interface bus.
+ * It is intended to be used in situation where hardware spi is not available.
+ * The signatures of the functions are similar to the functions declared in spi.h
+ * The clock speed is approximated by using xtimer_nanosleep.
+ * Currently only the use of MOSI in master mode is implemented. Therefore receiving
+ * data from a slave is currently not possible.
+ * @{
+ *
+ * @file
+ * @brief       Software SPI port descriptor definition
+ *
+ * @author      Markus Blechschmidt <Markus.Blechschmidt@haw-hamburg.de>
+ */
+
+#ifndef SOFT_SPI_H
+#define SOFT_SPI_H
+
+#include "periph/gpio.h"
+#include "periph/spi.h"
+#include "mutex.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief   Default SPI device access macro
+ */
+#ifndef SOFT_SPI_DEV
+#define SOFT_SPI_DEV(x)      (x)
+#endif
+
+/**
+ * @brief   Define global value for undefined SPI device
+ */
+#ifndef SOFT_SPI_UNDEF
+#define SOFT_SPI_UNDEF       (UINT_MAX)
+#endif
+
+/**
+ * @brief   Define value for unused CS line
+ */
+#ifndef SOFT_SPI_CS_UNDEF
+#define SOFT_SPI_CS_UNDEF    (GPIO_UNDEF)
+#endif
+
+/**
+ * @brief   Default type for SPI devices
+ */
+typedef unsigned int soft_spi_t;
+
+/**
+ * @brief   Chip select pin type overlaps with gpio_t so it can be casted to
+ *          this
+ */
+typedef gpio_t soft_spi_cs_t;
+
+/**
+ * @brief   Status codes used by the SPI driver interface
+ */
+enum {
+    SOFT_SPI_OK          =  0,   /**< everything went as planned */
+    SOFT_SPI_NODEV       = -1,   /**< invalid SPI bus specified */
+    SOFT_SPI_NOCS        = -2,   /**< invalid chip select line specified */
+    SOFT_SPI_NOMODE      = -3,   /**< selected mode is not supported */
+    SOFT_SPI_NOCLK       = -4    /**< selected clock value is not supported */
+};
+
+/**
+ * @brief   Available SPI modes, defining the configuration of clock polarity
+ *          and clock phase
+ *
+ * RIOT is using the mode numbers as commonly defined by most vendors
+ * (https://en.wikipedia.org/wiki/Serial_Peripheral_Interface_Bus#Mode_numbers):
+ *
+ * - MODE_0: CPOL=0, CPHA=0 - The first data bit is sampled by the receiver on
+ *           the first SCK rising SCK edge (this mode is used most often).
+ * - MODE_1: CPOL=0, CPHA=1 - The first data bit is sampled by the receiver on
+ *           the second rising SCK edge.
+ * - MODE_2: CPOL=1, CPHA=0 - The first data bit is sampled by the receiver on
+ *           the first falling SCK edge.
+ * - MODE_3: CPOL=1, CPHA=1 - The first data bit is sampled by the receiver on
+ *           the second falling SCK edge.
+ */
+typedef enum {
+    SOFT_SPI_MODE_0 = 0,         /**< CPOL=0, CPHA=0 */
+    SOFT_SPI_MODE_1,             /**< CPOL=0, CPHA=1 */
+    SOFT_SPI_MODE_2,             /**< CPOL=1, CPHA=0 */
+    SOFT_SPI_MODE_3              /**< CPOL=1, CPHA=1 */
+} soft_spi_mode_t;
+
+/**
+ * @brief   Available SPI clock speeds
+ *
+ * The actual speed of the bus varies between CPUs and depends on the speed
+ * of the processing. The values of the enum entries represent the approximate
+ * delay between two clock edges.
+ */
+typedef enum {
+    SOFT_SPI_CLK_100KHZ = 5000,     /**< drive the SPI bus with less than 100kHz */
+    SOFT_SPI_CLK_400KHZ = 1250,     /**< drive the SPI bus with less than 400kHz */
+    SOFT_SPI_CLK_DEFAULT = 0,       /**< drive the SPI bus with maximum speed possible */
+} soft_spi_clk_t;
+
+/**
+ * @brief Software SPI port descriptor
+ */
+typedef struct {
+    gpio_t miso_pin;                /**< MOSI pin */
+    gpio_t mosi_pin;                /**< MOSI pin */
+    gpio_t clk_pin;                 /**< CLK pin */
+    soft_spi_mode_t soft_spi_mode;  /**< data and clock polarity */
+    soft_spi_clk_t soft_spi_clk;    /**< clock speed */
+} soft_spi_conf_t;
+
+/**
+ * @brief   Basic initialization of the given SPI bus
+ *
+ * This function does the basic initialization including pin configuration for
+ * MISO, MOSI, and CLK pins.
+ *
+ * Errors (e.g. invalid @p bus parameter) are not signaled through a return
+ * value, but should be signaled using the assert() function internally.
+ *
+ * @note    This function MUST not be called more than once per bus!
+ *
+ * @param[in] bus       SPI device to initialize
+ */
+void soft_spi_init(soft_spi_t bus);
+
+/**
+ * @brief   Initialize the used SPI bus pins, i.e. MISO, MOSI, and CLK
+ *
+ * After calling soft_spi_init, the pins must be initialized. In normal cases,
+ * this function will not be used.
+ *
+ * The pins used are configured in the board's periph_conf.h.
+ *
+ * @param[in] bus       SPI device the pins are configure for
+ */
+void soft_spi_init_pins(soft_spi_t bus);
+
+/**
+ * @brief   Initialize the given chip select pin
+ *
+ * The chip select must be any generic GPIO pin (e.g. GPIO_PIN(x,y)). It must be
+ * called once before the use of the chip select pin in transaction.
+ *
+ * @param[in] bus       SPI device that is used with the given CS line
+ * @param[in] cs        chip select pin to initialize
+ *
+ * @return              SOFT_SPI_OK on success
+ * @return              SOFT_SPI_NODEV on invalid device
+ * @return              SOFT_SPI_NOCS on invalid CS pin/line
+ */
+int soft_spi_init_cs(soft_spi_t bus, soft_spi_cs_t cs);
+
+/**
+ * @brief   Start a new SPI transaction
+ *
+ * Starting a new SPI transaction will get exclusive access to the SPI bus
+ * and configure it according to the given values. If another SPI transaction
+ * is active when this function is called, this function will block until the
+ * other transaction is complete (soft_spi_relase was called).
+ *
+ * @note    This function expects the @p bus and the @p cs parameters to be
+ *          valid (they are checked in soft_spi_init and soft_spi_init_cs before)
+ *
+ * @param[in] bus       SPI device to access
+ * @param[in] cs        chip select pin/line to use
+ * @param[in] mode      mode to use for the new transaction
+ * @param[in] clk       bus clock speed to use for the transaction
+ *
+ * @return              SOFT_SPI_OK on success
+ * @return              SOFT_SPI_NOMODE if given mode is not supported
+ * @return              SOFT_SPI_NOCLK if given clock speed is not supported
+ */
+int soft_spi_acquire(soft_spi_t bus, soft_spi_cs_t cs, soft_spi_mode_t mode, soft_spi_clk_t clk);
+
+/**
+ * @brief   Finish an ongoing SPI transaction by releasing the given SPI bus
+ *
+ * After release, the given SPI bus should be fully powered down until acquired
+ * again.
+ *
+ * @param[in] bus       SPI device to release
+ */
+void soft_spi_release(soft_spi_t bus);
+
+/**
+ * @brief Transfer one byte on the given SPI bus
+ * Currently only the use of MOSI in master mode is implemented. Therefore receiving
+ * data from a slave is currently not possible.
+ *
+ * @param[in] bus       SPI device to use
+ * @param[in] cs        chip select pin/line to use
+ * @param[in] cont      if true, keep device selected after transfer
+ * @param[in] out       byte to send out, set NULL if only receiving
+ *
+ * @return              the received byte
+ */
+uint8_t soft_spi_transfer_byte(soft_spi_t bus, soft_spi_cs_t cs, bool cont, uint8_t out);
+
+/**
+ * @brief   Transfer a number bytes using the given SPI bus
+ *
+ * @param[in]  bus      SPI device to use
+ * @param[in]  cs       chip select pin/line to use
+ * @param[in]  cont     if true, keep device selected after transfer
+ * @param[in]  out      buffer to send data from, set NULL if only receiving
+ * @param[out] in       buffer to read into, set NULL if only sending
+ * @param[in]  len      number of bytes to transfer
+ */
+void soft_spi_transfer_bytes(soft_spi_t bus, soft_spi_cs_t cs, bool cont,
+                        const void *out, void *in, size_t len);
+
+/**
+ * @brief   Transfer one byte to/from a given register address
+ *
+ * This function is a shortcut function for easier handling of SPI devices that
+ * implement a register based access scheme.
+ *
+ * @param[in] bus       SPI device to use
+ * @param[in]  cs       chip select pin/line to use
+ * @param[in] reg       register address to transfer data to/from
+ * @param[in] out       byte to send, set NULL if only receiving data
+ *
+ * @return              value that was read from the given register address
+ */
+uint8_t soft_spi_transfer_reg(soft_spi_t bus, soft_spi_cs_t cs, uint8_t reg, uint8_t out);
+
+/**
+ * @brief   Transfer a number of bytes to/from a given register address
+ *
+ * This function is a shortcut function for easier handling of SPI devices that
+ * implement a register based access scheme.
+ *
+ * @param[in]  bus      SPI device to use
+ * @param[in]  cs       chip select pin/line to use
+ * @param[in]  reg      register address to transfer data to/from
+ * @param[in]  out      buffer to send data from, set NULL if only receiving
+ * @param[out] in       buffer to read into, set NULL if only sending
+ * @param[in]  len      number of bytes to transfer
+ */
+void soft_spi_transfer_regs(soft_spi_t bus, soft_spi_cs_t cs, uint8_t reg,
+                       const void *out, void *in, size_t len);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SOFT_SPI_H */
+/** @} */
diff --git a/drivers/soft_spi/Makefile b/drivers/soft_spi/Makefile
new file mode 100644
index 0000000000..48422e909a
--- /dev/null
+++ b/drivers/soft_spi/Makefile
@@ -0,0 +1 @@
+include $(RIOTBASE)/Makefile.base
diff --git a/drivers/soft_spi/include/soft_spi_params.h b/drivers/soft_spi/include/soft_spi_params.h
new file mode 100644
index 0000000000..0dda874a97
--- /dev/null
+++ b/drivers/soft_spi/include/soft_spi_params.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2017 Hamburg University of Applied Sciences
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     driver_soft_spi
+ * @{
+ *
+ * @file
+ * @brief       Software SPI configuration
+ *
+ * @author      Markus Blechschmidt <Markus.Blechschmidt@haw-hamburg.de>
+ */
+
+#ifndef SOFT_SPI_PARAMS_H
+#define SOFT_SPI_PARAMS_H
+
+#include "soft_spi.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef SOFT_SPI_PARAM_MISO
+#define SOFT_SPI_PARAM_MISO         (GPIO_UNDEF)
+#endif
+#ifndef SOFT_SPI_PARAM_MOSI
+#define SOFT_SPI_PARAM_MOSI         (GPIO_PIN(0, 0))
+#endif
+#ifndef SOFT_SPI_PARAM_CLK
+#define SOFT_SPI_PARAM_CLK          (GPIO_PIN(0, 1))
+#endif
+
+#define SOFT_SPI_PARAMS_DEFAULT     {\
+                                        .miso_pin = SOFT_SPI_PARAM_MISO,\
+                                        .mosi_pin = SOFT_SPI_PARAM_MOSI,\
+                                        .clk_pin  = SOFT_SPI_PARAM_CLK,\
+                                    }
+
+/**
+ * @brief   Sotware SPI port descriptor array
+ */
+static soft_spi_conf_t soft_spi_config[] = {
+#ifdef SOFT_SPI_PARAMS_CUSTOM
+    SOFT_SPI_PARAMS_CUSTOM,
+#else
+    SOFT_SPI_PARAMS_DEFAULT,
+#endif
+};
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* SOFT_SPI_PARAMS_H */
+/** @} */
diff --git a/drivers/soft_spi/soft_spi.c b/drivers/soft_spi/soft_spi.c
new file mode 100644
index 0000000000..9bec90bdcd
--- /dev/null
+++ b/drivers/soft_spi/soft_spi.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2017 Hamburg University of Applied Sciences
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @ingroup     driver_soft_spi
+ * @{
+ *
+ * @file
+ * @brief       Software SPI implementation
+ *
+ * @author      Markus Blechschmidt <Markus.Blechschmidt@haw-hamburg.de>
+ * @author      Peter Kietzmann     <peter.kietzmann@haw-hamburg.de>
+ */
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "mutex.h"
+#include "periph/gpio.h"
+#include "xtimer.h"
+
+#include "soft_spi.h"
+#include "soft_spi_params.h"
+
+#define ENABLE_DEBUG (0)
+#include "debug.h"
+
+/**
+ * @brief   Allocate one lock per SPI device
+ */
+static mutex_t locks[sizeof soft_spi_config];
+
+static inline bool soft_spi_bus_is_valid(soft_spi_t bus)
+{
+    unsigned int soft_spi_num = (unsigned int) bus;
+
+    if (sizeof(soft_spi_config) / sizeof(soft_spi_config[0]) < soft_spi_num) {
+        return false;
+    }
+    return true;
+}
+
+void soft_spi_init(soft_spi_t bus)
+{
+    DEBUG("Soft SPI init\n");
+
+    assert(soft_spi_bus_is_valid(bus));
+
+    /* initialize device lock */
+    mutex_init(&locks[bus]);
+    soft_spi_init_pins(bus);
+}
+
+void soft_spi_init_pins(soft_spi_t bus)
+{
+    DEBUG("Soft SPI soft_spi_init_pins\n");
+
+    assert(soft_spi_bus_is_valid(bus));
+
+    /* check that miso is not mosi is not clk*/
+    assert(soft_spi_config[bus].mosi_pin != soft_spi_config[bus].miso_pin);
+    assert(soft_spi_config[bus].mosi_pin != soft_spi_config[bus].clk_pin);
+    assert(soft_spi_config[bus].miso_pin != soft_spi_config[bus].clk_pin);
+    /* mandatory pins */
+    assert((GPIO_UNDEF != soft_spi_config[bus].mosi_pin) || (GPIO_UNDEF != soft_spi_config[bus].miso_pin));
+    assert(GPIO_UNDEF != soft_spi_config[bus].clk_pin);
+
+    /* initialize clock pin */
+    gpio_init(soft_spi_config[bus].clk_pin, GPIO_OUT);
+    /* initialize optional pins */
+    if (GPIO_UNDEF != soft_spi_config[bus].mosi_pin) {
+        gpio_init(soft_spi_config[bus].mosi_pin, GPIO_OUT);
+        gpio_clear(soft_spi_config[bus].mosi_pin);
+    }
+    if (GPIO_UNDEF != soft_spi_config[bus].miso_pin) {
+        gpio_init(soft_spi_config[bus].mosi_pin, GPIO_IN);
+    }
+}
+
+int soft_spi_init_cs(soft_spi_t bus, soft_spi_cs_t cs)
+{
+    DEBUG("Soft SPI init CS\n");
+    if (!soft_spi_bus_is_valid(bus)) {
+        DEBUG("Soft SPI bus not valid\n");
+        return SOFT_SPI_NODEV;
+    }
+
+    if ((cs != GPIO_UNDEF) && (cs != SOFT_SPI_CS_UNDEF)) {
+        DEBUG("Soft SPI set user CS line\n");
+        gpio_init(cs, GPIO_OUT);
+        gpio_set(cs);
+    }
+
+    return SOFT_SPI_OK;
+}
+
+int soft_spi_acquire(soft_spi_t bus, soft_spi_cs_t cs, soft_spi_mode_t mode, soft_spi_clk_t clk)
+{
+    (void) cs;
+    assert(soft_spi_bus_is_valid(bus));
+
+    /* lock bus */
+    mutex_lock(&locks[bus]);
+
+    if ((mode != SOFT_SPI_MODE_0) && (mode != SOFT_SPI_MODE_1) &&
+        (mode != SOFT_SPI_MODE_2) && (mode != SOFT_SPI_MODE_3)) {
+        return SOFT_SPI_NOMODE;
+    }
+    soft_spi_config[bus].soft_spi_mode = mode;
+    switch (mode) {
+        case SOFT_SPI_MODE_0:
+        case SOFT_SPI_MODE_1:
+            /* CPOL=0 */
+            gpio_clear(soft_spi_config[bus].clk_pin);
+            break;
+        case SOFT_SPI_MODE_2:
+        case SOFT_SPI_MODE_3:
+            /* CPOL=1 */
+            gpio_set(soft_spi_config[bus].clk_pin);
+            break;
+    }
+    soft_spi_config[bus].soft_spi_clk = clk;
+    return SOFT_SPI_OK;
+}
+
+void soft_spi_release(soft_spi_t bus)
+{
+    assert(soft_spi_bus_is_valid(bus));
+    mutex_unlock(&locks[bus]);
+}
+
+static inline uint8_t _transfer_one_byte(soft_spi_t bus, uint8_t out)
+{
+    int8_t bit = 0, i = 0;
+    if (SOFT_SPI_MODE_1 == soft_spi_config[bus].soft_spi_mode ||
+        SOFT_SPI_MODE_3 == soft_spi_config[bus].soft_spi_mode) {
+        /* CPHA = 1*/
+        gpio_toggle(soft_spi_config[bus].clk_pin);
+    }
+
+    bit = (out & (1 << 7)) >> 7;
+    gpio_write(soft_spi_config[bus].mosi_pin, bit);
+    for (i = 6; i >= 0; i--) {
+        xtimer_nanosleep(soft_spi_config[bus].soft_spi_clk);
+        gpio_toggle(soft_spi_config[bus].clk_pin);
+        xtimer_nanosleep(soft_spi_config[bus].soft_spi_clk);
+        gpio_toggle(soft_spi_config[bus].clk_pin);
+        bit = (out & (1 << i)) >> i;
+        gpio_write(soft_spi_config[bus].mosi_pin, bit);
+    }
+    xtimer_nanosleep(soft_spi_config[bus].soft_spi_clk);
+    gpio_toggle(soft_spi_config[bus].clk_pin);
+
+    if (SOFT_SPI_MODE_0 == soft_spi_config[bus].soft_spi_mode ||
+        SOFT_SPI_MODE_2 == soft_spi_config[bus].soft_spi_mode) {
+        /* CPHASE = 1 */
+        xtimer_nanosleep(soft_spi_config[bus].soft_spi_clk);
+        gpio_toggle(soft_spi_config[bus].clk_pin);
+    }
+
+    return out;
+}
+
+uint8_t soft_spi_transfer_byte(soft_spi_t bus, soft_spi_cs_t cs, bool cont, uint8_t out)
+{
+    DEBUG("Soft SPI soft_spi_transfer_bytes\n");
+    assert(soft_spi_bus_is_valid(bus));
+
+    uint8_t retval = 0;
+
+    /* activate the given chip select line */
+    if ((cs != GPIO_UNDEF) && (cs != SOFT_SPI_CS_UNDEF)) {
+        gpio_clear((gpio_t)cs);
+    }
+
+    retval = _transfer_one_byte(bus, out);
+
+    if (!cont) {
+        if ((cs != GPIO_UNDEF) && (cs != SOFT_SPI_CS_UNDEF)) {
+            gpio_set((gpio_t)cs);
+        }
+    }
+
+    return retval;
+}
+
+void soft_spi_transfer_bytes(soft_spi_t bus, soft_spi_cs_t cs, bool cont,
+                        const void *out, void *in, size_t len)
+{
+    DEBUG("Soft SPI soft_spi_transfer_bytes\n");
+
+    assert(soft_spi_bus_is_valid(bus));
+
+    uint8_t tmp = 0;
+
+    for (size_t i = 0; i < len-1; i++) {
+        tmp = (NULL != out) ? ((uint8_t *)out)[i] : 0;
+        uint8_t retval = soft_spi_transfer_byte(bus, cs, true, tmp);
+        if (NULL != in) {
+            ((uint8_t *)in)[0] = retval;
+        }
+    }
+
+    tmp = (NULL != out) ? ((uint8_t *)out)[len-1] : 0;
+    soft_spi_transfer_byte(bus, cs, cont, tmp);
+}
diff --git a/tests/driver_soft_spi/Makefile b/tests/driver_soft_spi/Makefile
new file mode 100644
index 0000000000..5bfe2fe7d3
--- /dev/null
+++ b/tests/driver_soft_spi/Makefile
@@ -0,0 +1,16 @@
+APPLICATION = driver_soft_spi
+include ../Makefile.tests_common
+
+BOARD ?= native
+
+USEMODULE += soft_spi
+
+# set Soft SPI bus and pins to default values
+TEST_SOFT_SPI_DEV ?= SOFT_SPI_DEV\(0\)
+TEST_CS_PIN ?= GPIO_PIN\(0,2\)
+
+# export SPI and pins
+CFLAGS += -DTEST_SOFT_SPI_DEV=$(TEST_SOFT_SPI_DEV)
+CFLAGS += -DTEST_CS_PIN=$(TEST_CS_PIN)
+
+include $(RIOTBASE)/Makefile.include
diff --git a/tests/driver_soft_spi/README.md b/tests/driver_soft_spi/README.md
new file mode 100644
index 0000000000..4e996ef745
--- /dev/null
+++ b/tests/driver_soft_spi/README.md
@@ -0,0 +1,12 @@
+Expected result
+===============
+The test code transmits one byte with each SPI mode and a string over the
+software SPI MOSI line. This can be verified with a Logic Analyzer connected to
+the corresponding pins. When you see 0xa5 in all four SPI modes and the string
+"Soft SPI Test String" in SPI mode 0 being transmitted via the CLK and MOSI pin,
+the test is successful.
+
+Notes
+==========
+- Because the module does not cover MISO inputs, neither does the test.
+- The definition of SOFT SPI devices is done via the CFLAGS in the Makefile
diff --git a/tests/driver_soft_spi/main.c b/tests/driver_soft_spi/main.c
new file mode 100644
index 0000000000..2423587e59
--- /dev/null
+++ b/tests/driver_soft_spi/main.c
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2017 Hamburg University of Applied Sciences
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser General
+ * Public License v2.1. See the file LICENSE in the top level directory for more
+ * details.
+ */
+
+/**
+ * @ingroup tests
+ * @{
+ *
+ * @file
+ * @brief       Application for testing the software SPI driver implementations
+ *
+ *
+ * @author      Markus Blechschmidt <Markus.Blechschmidt@haw-hamburg.de>
+ * @author      Peter Kietzmann     <peter.kietzmann@haw-hamburg.de>
+ *
+ * @}
+ */
+
+#include <stdio.h>
+#include <string.h>
+
+#include "soft_spi.h"
+
+int main(void)
+{
+    puts("Minimal test application for the software SPI driver");
+
+    char string[] = "Soft SPI Test String";
+
+    soft_spi_t soft_spi = TEST_SOFT_SPI_DEV;
+    soft_spi_cs_t cs = TEST_CS_PIN;
+
+    /* Initialize software SPI device  */
+    soft_spi_init(soft_spi);
+
+    /* Initialize CS pin */
+    int tmp = soft_spi_init_cs(soft_spi, cs);
+    if (tmp != SOFT_SPI_OK) {
+        printf("error: unable to initialize the given chip select line %i\n", tmp);
+        return 1;
+    }
+
+    puts("Send 0xa5 in all four modes");
+    soft_spi_acquire(soft_spi, cs, SOFT_SPI_MODE_0, SOFT_SPI_CLK_100KHZ);
+    soft_spi_transfer_byte(soft_spi, cs, false, 0xa5);
+    soft_spi_release(soft_spi);
+
+    soft_spi_acquire(soft_spi, cs, SOFT_SPI_MODE_1, SOFT_SPI_CLK_100KHZ);
+    soft_spi_transfer_byte(soft_spi, cs, false, 0xa5);
+    soft_spi_release(soft_spi);
+
+    soft_spi_acquire(soft_spi, cs, SOFT_SPI_MODE_2, SOFT_SPI_CLK_100KHZ);
+    soft_spi_transfer_byte(soft_spi, cs, false, 0xa5);
+    soft_spi_release(soft_spi);
+
+    soft_spi_acquire(soft_spi, cs, SOFT_SPI_MODE_3, SOFT_SPI_CLK_100KHZ);
+    soft_spi_transfer_byte(soft_spi, cs, false, 0xa5);
+    soft_spi_release(soft_spi);
+
+    printf("Send %s\n",string);
+    soft_spi_acquire(soft_spi, cs, SOFT_SPI_MODE_0, SOFT_SPI_CLK_100KHZ);
+    soft_spi_transfer_bytes(soft_spi, cs, false, string, NULL, sizeof string);
+    soft_spi_release(soft_spi);
+
+    puts("Soft SPI Test End");
+    return 0;
+}
-- 
GitLab