通过普通IO模拟SPI主机

本文介绍了如何使用STM32L4的普通GPIO模拟SPI主机,包括初始化、写入、读取和同步操作,适用于没有硬件SPI接口时与SPI设备通信。提供了详细的代码实现和测试过程,通过逻辑分析仪验证了不同SPI模式下的时序正确性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 之前介绍过I2C协议的模拟,本次介绍另一个常用的协议SPI,使用普通GPIO模拟SPI主机。由于正常使用中SPI频率都比较高,模拟SPI的频率比较低,并不常用。通常在SPI读一些传感器数据,而又没有硬件SPI可用时,可以通过普通GPIO模拟SPI。

 模拟SPI时序要比I2C简单,根据时钟收发数据即可,不过SPI分很多种模式(CPOL,CPHA),LSB在前还是MSB在前等。

1. 模拟SPI主机代码

 这里使用的是Stm32L4的开发板,基于HAL库实现了GPIO模拟SPI主机,可以支持模拟多个SPI接口,不同接口可以有不同的SCK频率。Demo中在80MHz主频下,粗略设置了200K和500KHz SCK频率,需要精确频率可以通过定时器获得。

 头文件为:

#ifndef __SPI_SIMU_H
#define __SPI_SIMU_H
#ifdef __cplusplus
extern "C" {
#endif /*_cplusplus*/

#include "stm32l4xx_hal.h"

typedef struct _SPI_PortDef
{
    uint32_t        SPI_Timing;
    uint8_t         CPOL;
    uint8_t         CPHA;
    uint8_t         BitOrder;

    GPIO_TypeDef*   NSS_GPIOx;
    uint32_t        NSS_Pin;
    GPIO_TypeDef*   SCK_GPIOx;
    uint32_t        SCK_Pin;
    GPIO_TypeDef*   MISO_GPIOx;
    uint32_t        MISO_Pin;
    GPIO_TypeDef*   MOSI_GPIOx;
    uint32_t        MOSI_Pin;

    // internal
    void (*Sync)(struct _SPI_PortDef*, uint8_t, uint8_t*);
    GPIO_PinState idle;
    GPIO_PinState active;
} SPI_PortDef;

#define SPI_TIMING_200K      30
#define SPI_TIMING_500K      4
#define SPI_BYTE_TIMEOUT      10u
#define SPI_PIN_MODE          GPIO_MODE_OUTPUT_PP
#define SPI_MSB_FIRST         0
#define SPI_LSB_FIRST         1

#define ENTER_CRITICAL()
#define EXIT_CRITICAL()

void SPI_SimInit(SPI_PortDef* port);
void SPI_SimNSS(SPI_PortDef* port, uint8_t state);
HAL_StatusTypeDef SPI_SimWrite(SPI_PortDef* port, 
                               uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef SPI_SimRead(SPI_PortDef* port, 
                              uint8_t *data, uint32_t len, uint32_t Timeout);
HAL_StatusTypeDef SPI_SimSync(SPI_PortDef* port, 
                                  uint8_t *indata, uint8_t *outdata, 
                                  uint32_t len, uint32_t Timeout);
#ifdef __cplusplus
}
#endif /*_cplusplus*/
#endif /* __SPI_SIMU_H */

 实现文件为:

#include "spi_sim.h"

static void SysCtlDelay(uint32_t ulCount)
{
    __asm("    subs    r0, #1\n"
          "    bne.n   SysCtlDelay\n"
          "    bx      lr");
}

static void _SPI_DelayUs(uint32_t us)
{
    SysCtlDelay(us*2);
}

void _SPI_RCC_ENABLE(GPIO_TypeDef* GPIOx)
{
    if (GPIOx == GPIOA)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    } else if (GPIOx == GPIOB) {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    } else if (GPIOx == GPIOC) {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }
    #if defined(GPIOD)
    else if (GPIOx == GPIOD) {
        __HAL_RCC_GPIOD_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOE)
    else if (GPIOx == GPIOE) {
        __HAL_RCC_GPIOE_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOF)
    else if (GPIOx == GPIOF) {
        __HAL_RCC_GPIOF_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOG)
    else if (GPIOx == GPIOG) {
        __HAL_RCC_GPIOG_CLK_ENABLE();
    }
    #endif
}

void _SPI_PinOutPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
    GPIO_InitTypeDef  GPIO_InitStruct;
    GPIO_InitStruct.Mode  = SPI_PIN_MODE;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = Pin;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}

void _SPI_PinInPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
    GPIO_InitTypeDef  GPIO_InitStruct;
    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = Pin;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}

/* CPOL=0 & CPOL=1, CPHA=0 */
void SPI_SimSyncByteM0(SPI_PortDef* port, uint8_t chr, uint8_t* out)
{
    uint8_t i;
    uint8_t tmp = 0;

    ENTER_CRITICAL();
    HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
    if (port->BitOrder == SPI_MSB_FIRST)
    {
        for (i=0;i<8u;i++)
        {
            if (chr & 0x80)
            {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
            } else {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
            tmp <<= 1;
            if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
            {
                tmp |= 0x01;
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
            chr <<= 1u;
        }
    } else {
        for (i=0;i<8u;i++)
        {
            if (chr & 0x01)
            {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
            } else {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
            tmp >>= 1;
            if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
            {
                tmp |= 0x80;
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
            chr >>= 1u;
        }
    }
    *out = tmp;
    EXIT_CRITICAL();
}

/* CPOL=0 & CPOL=1, CPHA=1 */
void SPI_SimSyncByteM1(SPI_PortDef* port, uint8_t chr, uint8_t* out)
{
    uint8_t i;
    uint8_t tmp = 0;

    ENTER_CRITICAL();
    HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
    if (port->BitOrder == SPI_MSB_FIRST)
    {
        for (i=0;i<8u;i++)
        {
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
            if (chr & 0x80)
            {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
            } else {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
            tmp <<= 1;
            if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
            {
                tmp |= 0x01;
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            chr <<= 1u;
        }
    } else {
        for (i=0;i<8u;i++)
        {
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->active);
            if (chr & 0x01)
            {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
            } else {
                HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_RESET);
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, port->idle);
            tmp >>= 1;
            if (HAL_GPIO_ReadPin(port->MISO_GPIOx, port->MISO_Pin) == GPIO_PIN_SET)
            {
                tmp |= 0x80;
            }
            _SPI_DelayUs(port->SPI_Timing>>1);
            chr >>= 1u;
        }
    }
    *out = tmp;
    EXIT_CRITICAL();
}

void SPI_SimInit(SPI_PortDef* port)
{
    _SPI_RCC_ENABLE(port->NSS_GPIOx);
    _SPI_RCC_ENABLE(port->SCK_GPIOx);
    _SPI_RCC_ENABLE(port->MISO_GPIOx);
    _SPI_RCC_ENABLE(port->MOSI_GPIOx);

    HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->MISO_GPIOx, port->MISO_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->MOSI_GPIOx, port->MOSI_Pin, GPIO_PIN_SET);
    if (port->CPOL == 1){
        HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, GPIO_PIN_SET);
    } else {
        HAL_GPIO_WritePin(port->SCK_GPIOx, port->SCK_Pin, GPIO_PIN_RESET);
    }
    _SPI_PinOutPut(port->NSS_GPIOx, port->NSS_Pin);
    _SPI_PinInPut(port->MISO_GPIOx, port->MISO_Pin);
    _SPI_PinOutPut(port->MOSI_GPIOx, port->MOSI_Pin);
    _SPI_PinOutPut(port->SCK_GPIOx, port->SCK_Pin);
    if ((port->CPOL == 0) && (port->CPHA == 0))
    {
        port->Sync = SPI_SimSyncByteM0;
        port->idle = GPIO_PIN_RESET;
        port->active = GPIO_PIN_SET;
    } else if ((port->CPOL == 0) && (port->CPHA == 1)) {
        port->Sync = SPI_SimSyncByteM1;
        port->idle = GPIO_PIN_RESET;
        port->active = GPIO_PIN_SET;
    } else if ((port->CPOL == 1) && (port->CPHA == 0)) {
        port->Sync = SPI_SimSyncByteM0;
        port->idle = GPIO_PIN_SET;
        port->active = GPIO_PIN_RESET;
    } else if ((port->CPOL == 1) && (port->CPHA == 1)) {
        port->Sync = SPI_SimSyncByteM1;
        port->idle = GPIO_PIN_SET;
        port->active = GPIO_PIN_RESET;
    } else {
        port->Sync = SPI_SimSyncByteM0;
        port->idle = GPIO_PIN_RESET;
        port->active = GPIO_PIN_SET;
    }
}

void SPI_SimNSS(SPI_PortDef* port, uint8_t state)
{
    if (state) {
        HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_SET);
    } else {
        HAL_GPIO_WritePin(port->NSS_GPIOx, port->NSS_Pin, GPIO_PIN_RESET);
    }
}

HAL_StatusTypeDef SPI_WaitTill(SPI_PortDef* port, uint32_t start, uint32_t Timeout)
{
    if ((HAL_GetTick()-start) > Timeout)
    {
        return HAL_TIMEOUT;
    }
    return HAL_OK;
}

HAL_StatusTypeDef SPI_SimWrite(SPI_PortDef* port, 
                               uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint8_t tmp;
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();
    for (i=0;i<len;i++)
    {
        port->Sync(port, data[i], &tmp);
        if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    return HAL_OK;
}


HAL_StatusTypeDef SPI_SimRead(SPI_PortDef* port, 
                              uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint8_t tmp = 0;
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();
    for (i=0;i<len;i++)
    {
        port->Sync(port, tmp, data + i);
        if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    return HAL_OK;
}

HAL_StatusTypeDef SPI_SimSync(SPI_PortDef* port, 
                                  uint8_t *indata, uint8_t *outdata, 
                                  uint32_t len, uint32_t Timeout)
{
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();
    for (i=0;i<len;i++)
    {
        port->Sync(port, indata[i], outdata + i);
        if (SPI_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    return HAL_OK;
}

2.测试代码

 这里直接通过逻辑分析仪分析数据正确性。测试代码为:

uint8_t data[5];
uint8_t data_out[5];
SPI_PortDef spiport;

spiport.SPI_Timing = SPI_TIMING_500K;
spiport.CPOL = 0; //0,1
spiport.CPHA = 0; //0,1
spiport.BitOrder = SPI_MSB_FIRST; //SPI_MSB_FIRST
spiport.NSS_GPIOx = GPIOB;
spiport.NSS_Pin = GPIO_PIN_12;
spiport.SCK_GPIOx = GPIOB;
spiport.SCK_Pin = GPIO_PIN_13;
spiport.MISO_GPIOx = GPIOB;
spiport.MISO_Pin = GPIO_PIN_14;
spiport.MOSI_GPIOx = GPIOB;
spiport.MOSI_Pin = GPIO_PIN_15;

for (uint8_t i=0;i<sizeof(data);i++)
{
    data[i] = i;
    data_out[i] = 0;
}

SPI_SimInit(&spiport);
SPI_SimNSS(&spiport, 0);
SPI_SimSync(&spiport, data, data_out, sizeof(data), 50);
SPI_SimNSS(&spiport, 1);

 通过逻辑分析仪抓到的数据可以看到SPI的时序是正确的:

 CPOL=0, CPHA=0, SPI_MSB_FIRST

 CPOL=0, CPHA=1, SPI_MSB_FIRST

 CPOL=1, CPHA=0, SPI_MSB_FIRST

 CPOL=1, CPHA=1, SPI_MSB_FIRST

3.总结

 通过GPIO模拟SPI主机灵活方便,可以同时模拟多个SPI端口,缺点是速度都比较慢,只适用一些特殊的情况,如同时测试多个SPI slave设备。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值