From fbba6c38514ae0ea395d96b444af048b9e1a1a91 Mon Sep 17 00:00:00 2001 From: marble 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 + */ + +#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 + */ + +#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 + * @author Peter Kietzmann + */ + +#include +#include + +#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 + * @author Peter Kietzmann + * + * @} + */ + +#include +#include + +#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; +}