From b196ce9cbcb1d03890b5e5dad1253756f5f78b2a Mon Sep 17 00:00:00 2001 From: Silvano Seva Date: Tue, 28 May 2024 21:07:11 +0200 Subject: [PATCH] Driver for bitbanged SPI on MCU gpios --- meson.build | 4 +- platform/drivers/SPI/spi_bitbang.c | 179 +++++++++++++++++++++++++++++ platform/drivers/SPI/spi_bitbang.h | 90 +++++++++++++++ 3 files changed, 272 insertions(+), 1 deletion(-) create mode 100644 platform/drivers/SPI/spi_bitbang.c create mode 100644 platform/drivers/SPI/spi_bitbang.h diff --git a/meson.build b/meson.build index 7162ea64..535edbe8 100644 --- a/meson.build +++ b/meson.build @@ -66,7 +66,8 @@ openrtx_src = ['openrtx/src/core/state.c', 'openrtx/src/protocols/M17/M17Demodulator.cpp', 'openrtx/src/protocols/M17/M17FrameEncoder.cpp', 'openrtx/src/protocols/M17/M17FrameDecoder.cpp', - 'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp'] + 'openrtx/src/protocols/M17/M17LinkSetupFrame.cpp', + 'platform/drivers/SPI/spi_bitbang.c'] openrtx_inc = ['openrtx/include', 'openrtx/include/rtx', @@ -78,6 +79,7 @@ openrtx_inc = ['openrtx/include', 'platform/drivers/ADC', 'platform/drivers/NVM', 'platform/drivers/GPS', + 'platform/drivers/SPI', 'platform/drivers/USB', 'platform/drivers/tones', 'platform/drivers/baseband', diff --git a/platform/drivers/SPI/spi_bitbang.c b/platform/drivers/SPI/spi_bitbang.c new file mode 100644 index 00000000..4ca6b5d9 --- /dev/null +++ b/platform/drivers/SPI/spi_bitbang.c @@ -0,0 +1,179 @@ +/*************************************************************************** + * Copyright (C) 2024 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#include +#include "spi_bitbang.h" + +typedef uint8_t (*sendRecv_impl)(struct spiConfig *cfg, uint8_t data); + +static uint8_t sendRecv_clkRising(struct spiConfig *cfg, uint8_t data) +{ + uint8_t incoming = 0; + + for(uint8_t cnt = 0; cnt < 8; cnt++) + { + // Setup new output towards the peripheral + if((data & 0x80) != 0) + gpio_setPin(cfg->mosi.port, cfg->mosi.pin); + else + gpio_clearPin(cfg->mosi.port, cfg->mosi.pin); + + // Sample the current incoming bit from the peripheral + data <<= 1; + incoming <<= 1; + incoming |= gpio_readPin(cfg->miso.port, cfg->miso.pin); + + // One clock cycle, peripheral reads the new bit and updates its output + // bit + delayUs(cfg->clkPeriod); + gpio_setPin(cfg->clk.port, cfg->clk.pin); + delayUs(cfg->clkPeriod); + gpio_clearPin(cfg->clk.port, cfg->clk.pin); + } + + return incoming; +} + +static uint8_t sendRecv_clkFalling(struct spiConfig *cfg, uint8_t data) +{ + uint8_t incoming = 0; + + for(uint8_t cnt = 0; cnt < 8; cnt++) + { + // Clock rising edge (inactive edge) + gpio_setPin(cfg->clk.port, cfg->clk.pin); + + // Setup data output + if((data & 0x80) != 0) + gpio_setPin(cfg->mosi.port, cfg->mosi.pin); + else + gpio_clearPin(cfg->mosi.port, cfg->mosi.pin); + + // Sample data input + data <<= 1; + incoming <<= 1; + incoming |= gpio_readPin(cfg->miso.port, cfg->miso.pin); + + // Clock falling edge, peripheral reads new data and updates its output + // line + delayUs(cfg->clkPeriod); + gpio_clearPin(cfg->clk.port, cfg->clk.pin); + delayUs(cfg->clkPeriod); + } + + return incoming; +} + + +int spiBitbang_init(const struct spiDevice* dev) +{ + struct spiConfig *cfg = (struct spiConfig *) dev->priv; + + // Setup MOSI and clock lines as output, low level + gpio_setMode(cfg->clk.port, cfg->clk.pin, OUTPUT); + gpio_setMode(cfg->mosi.port, cfg->mosi.pin, OUTPUT); + gpio_clearPin(cfg->mosi.port, cfg->mosi.pin); + gpio_clearPin(cfg->clk.port, cfg->clk.pin); + + // Set MISO line as input only for full-duplex operation + if((cfg->flags & SPI_HALF_DUPLEX) == 0) + gpio_setMode(cfg->miso.port, cfg->miso.pin, INPUT); + + // Set initial state of clock line to high if requested + if((cfg->flags & SPI_FLAG_CPOL) != 0) + gpio_clearPin(cfg->clk.port, cfg->clk.pin); + + if(dev->mutex != NULL) + pthread_mutex_init((pthread_mutex_t *) dev->mutex, NULL); + + return 0; + +} + +void spiBitbang_terminate(const struct spiDevice* dev) +{ + struct spiConfig *cfg = (struct spiConfig *) dev->priv; + + // Set clock and MOSI back to Hi-Z state + gpio_setMode(cfg->clk.port, cfg->clk.pin, INPUT); + gpio_setMode(cfg->mosi.port, cfg->mosi.pin, INPUT); + + if(dev->mutex != NULL) + pthread_mutex_destroy((pthread_mutex_t *) dev->mutex); +} + +int spiBitbang_impl(const struct spiDevice *dev, const void *txBuf, + const size_t txSize, void *rxBuf, const size_t rxSize) +{ + struct spiConfig *cfg = (struct spiConfig *) dev->priv; + const uint8_t *txData = (const uint8_t *) txBuf; + uint8_t *rxData = (uint8_t *) rxBuf; + sendRecv_impl spi_sendRecv; + + if((cfg->flags & SPI_FLAG_CPHA) != 0) + spi_sendRecv = &sendRecv_clkFalling; + else + spi_sendRecv = &sendRecv_clkRising; + + // Send only + if((rxBuf == NULL) || (rxSize == 0)) + { + for(size_t i = 0; i < txSize; i++) + spi_sendRecv(cfg, txData[i]); + + return 0; + } + + // Receive only + if((txBuf == NULL) || (txSize == 0)) + { + for(size_t i = 0; i < rxSize; i++) + rxData[i] = spi_sendRecv(cfg, 0x00); + + return 0; + } + + // Transmit and receive + size_t txRxSize = (txSize < rxSize) ? txSize : rxSize; + for(size_t i = 0; i < txRxSize; i++) + rxData[i] = spi_sendRecv(cfg, txData[i]); + + // Still something to send? + if(txSize > txRxSize) + { + for(size_t i = 0; i < (txSize - txRxSize); i++) + { + size_t pos = txRxSize + i; + spi_sendRecv(cfg, txData[pos]); + } + } + + // Still something to receive? + if(rxSize > txRxSize) + { + for(size_t i = 0; i < (rxSize - txRxSize); i++) + { + size_t pos = txRxSize + i; + rxData[pos] = spi_sendRecv(cfg, 0x00); + } + } + + return 0; +} diff --git a/platform/drivers/SPI/spi_bitbang.h b/platform/drivers/SPI/spi_bitbang.h new file mode 100644 index 00000000..b1df4260 --- /dev/null +++ b/platform/drivers/SPI/spi_bitbang.h @@ -0,0 +1,90 @@ +/*************************************************************************** + * Copyright (C) 2024 by Federico Amedeo Izzo IU2NUO, * + * Niccolò Izzo IU2KIN * + * Frederik Saraci IU2NRO * + * Silvano Seva IU2KWO * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 3 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, see * + ***************************************************************************/ + +#ifndef SPI_BITBANG_H +#define SPI_BITBANG_H + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define SCK_PERIOD_FROM_FREQ(x) (1000000/x) + +/** + * Data structure collecting the configuration data for the SPI bitbang driver. + * + * The driver uses the MCU native gpio driver for two reasons: + * 1) the faster the gpios are driver, the faster the SPI bitbang can work + * 2) using an external port expander to do SPI bitbang does not make sense... + */ +struct spiConfig +{ + const struct gpio clk; ///< SPI clock + const struct gpio mosi; ///< SPI data from MCU to peripherals + const struct gpio miso; ///< SPI data from peripherals to MCU + const uint32_t clkPeriod; ///< Clock period, in us + const uint8_t flags; ///< SPI configuration flags +}; + +/** + * Instantiate an SPI bitbang device. + * + * @param name: device name. + * @param mutx: pointer to mutex, or NULL. + * @param cfg: driver configuration data. + */ +#define SPI_BITBANG_DEVICE_DEFINE(name, mutx, cfg) \ +int spiBitbang_impl(const struct spiDevice *dev, const void *txBuf, \ + const size_t txSize, void *rxBuf, const size_t rxSize); \ +const struct spiDevice name = \ +{ \ + .transfer = spiBitbang_impl, \ + .priv = &cfg, \ + .mutex = mutx, \ +}; + +/** + * Initialise a bitbang SPI driver. + * Is left to application code to change the operating mode and alternate function + * mapping of the corresponding gpio lines. + * + * @param dev: SPI bitbang device descriptor. + * @param speed: SPI clock speed. + * @param flags: SPI configuration flags. + * @return zero on success, a negative error code otherwise. + */ +int spiBitbang_init(const struct spiDevice *dev); + +/** + * Shut down a bitbang SPI driver. + * + * @param dev: SPI bitbang device descriptor. + */ +void spiBitbang_terminate(const struct spiDevice *dev); + + +#ifdef __cplusplus +} +#endif + +#endif /* SPI_BITBANG_H */