libftdi1学习笔记 4 - MPSSE SPI

本文详细描述了一种采用纯GPIO模式实现SPI通信的方法,包括SCK电平设置、GPIO控制、SPI全双工通信机制以及如何读取FlashID。同时,文中给出了相关函数和步骤,如初始化、模式设置和GPIO操作,适用于SPI和QSPI设备。
摘要由CSDN通过智能技术生成

目录

1. 初始化

2. SCK默认电平设置

3. GPIO控制

4. spi全双工通信

4.1 MSB/LSB

4.2 分配command缓存

4.3 spi0TransferBit

4.3 spi1TransferBit

4.4 spi2TransferBit

4.5 spi3TransferBit

4.6 写命令序列

4.7 读数据

4.8 组合实际数据

5. 验证

5.1 初始化FTDI设备模式

5.2 初始化GPIO

5.3 控制CS

5.4 读flash的id

5.5 结果


与官方的方式不同,这里采用纯GPIO模式的方式实现SPI,这样可以定义任意GPIO为SPI,也可以实现QSPI。

typedef struct 
{
    struct ftdi_context *ftdi;
    
    uint8_t sck;
    uint8_t mosi_io0;
    uint8_t miso_io1;
    uint8_t io2;
    uint8_t io3;

    uint8_t freq;
    spi_type_e type;
    spi_mode_e mode;
    bool msb;
    uint8_t *pCommand;
    int iCommand;
}mpsse_spi_s;

sck/mosi_io0/miso_io1/io2/io3分别对应SPI/QSPI的io,同样,所有io都必须属于同一组。注意,CS脚单独控制,用GPIO的方式控制。

type表示该SPI是哪种类型:

typedef enum
{
    SPI_TYPE_SPI = 0,
    SPI_TYPE_QSPI,
}spi_type_e;

mode则表示spi的模式

由于多个SPI从设备可以共用sck和数据口,只使用不同的cs即可。所以这里只定义2个spi设备

typedef enum
{
    SPI_PORT_0 = 0,
    SPI_PORT_1,
    SPI_PORT_MAX,
}mpsse_spi_port_e;

mpsse_spi_s spi[SPI_PORT_MAX];

1. 初始化

void spiInit(uint8_t port, mpsse_spi_s init)
{
    if(port >= SPI_PORT_MAX)
        return;
    spi[port] = init;
    spi[port].freq += 1;
}

2. SCK默认电平设置

根据SPI的模式设置SCK默认电平,如果是模式0和模式1,SCK默认是低电平(CPOL = 0),如果是模式2和模式3(CPOL = 1),SCK默认是高电平。

void spiCPOL(uint8_t port)
{
    if(spi[port].mode == SPI_MODE0 || spi[port].mode == SPI_MODE1)
        mpsseGpioWrite(spi[port].sck, 0);
    else
        mpsseGpioWrite(spi[port].sck, 1);
}

这个函数要在CS设置为低前调用。

3. GPIO控制

将GPIO控制定义好宏

#define spiSCKHigh(port)        do{\
    gpio.level |= ((uint16_t)1 << spi[port].sck);\
}while(0)

#define spiSCKLow(port)        do{\
    gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].sck));\
}while(0)

#define spiMOSIHigh(port)       do{\
    gpio.level |= ((uint16_t)1 << spi[port].mosi_io0);\
}while(0)

#define spiMOSILow(port)        do{\
    gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].mosi_io0));\
}while(0)

#define spiMISORead(port)    do{\
    spi[port].pCommand[spi[port].iCommand++] = gpioReadCommand[gpioCommand(port)];\
}while(0)

4. spi全双工通信

spi类型下传输数据接口函数:

int spiTransferBytes(uint8_t port, uint8_t* wrBuf, uint8_t* rdBuf, uint16_t len)

这种模式下是全双工的模式,和选择的模式有关。CPHA表示选择第几个沿来

当CPHA = 0时,表示第一个沿采集数据(即读入数据);当CPHA = 1时,表示第二个沿采集数据。由于CPOL确定了SCK初始电平,所以这2个设置就决定了第一个是上升沿还是下降沿。

modeCPOLCPHA
mode 000
mode 101
mode 210
mode 311

mode 0: SCK初始电平为低电平, 在第一个上升沿前MOSI输出,然后在上升沿后MISO读入,完成第一个位的传输。

mode 1: SCK初始电平为低电平, 在第一个上升沿前MOSI输出,等到下降沿后MISO读入

mode 2: SCK初始电平为高电平, 在第一个下降沿前MOSI输出,然后在下降沿后MISO读入,完成第一个位的传输。

mode 3: SCK初始电平为高电平, 在第一个下降沿前MOSI输出,等到上升沿后MISO读入

    int size = len;
    int j = 0;
    while(size > 0)
    {
        int i;
        uint8_t wrDat;
        if(wrBuf == NULL)
            wrDat = 0xff;
        else
            wrDat = wrBuf[j];
        uint8_t level = 0; 
        for(i = 0; i < 8; i++)
        {
            if(spi[port].msb == false)
            {
                level = (wrDat & 0x01) ? 1 : 0;
                wrDat >>= 1;
            }
            else
            {
                level = (wrDat & 0x80) ? 1 : 0;
                wrDat <<= 1;
            }
            switch(spi[port].mode)
            {
                case SPI_MODE0:
                default:
                    spi0TransferBit(port, level);
                    break;

                case SPI_MODE1:
                    spi1TransferBit(port, level);
                    break;

                case SPI_MODE2:
                    spi2TransferBit(port, level);
                    break;

                case SPI_MODE3:
                    spi3TransferBit(port, level);
                    break;
            }
        }
        j++;
        size--;
    }

4.1 MSB/LSB

在发送前,先根据高位在前MSB还是低位在前LSB准备好要发送的位

        for(i = 0; i < 8; i++)
        {
            if(spi[port].msb == false)
            {
                level = (wrDat & 0x01) ? 1 : 0;
                wrDat >>= 1;
            }
            else
            {
                level = (wrDat & 0x80) ? 1 : 0;
                wrDat <<= 1;
            }
            switch(spi[port].mode)

4.2 分配command缓存

一个位写需要3 * 3 * freq个字节命令,最后需要加一个0x87命令,所以一共需要分配的字节数为:

    int commandlength = size * 3 * 3 * spi[port].freq + 1;
    spi[port].pCommand = (uint8_t *)malloc(commandlength);
    spi[port].iCommand = 0;

4.3 spi0TransferBit

模式0下传输一个位,在上升沿前MOSI输出位信息,然后产生上升沿,从设备准备好数据,主机读入数据,最后拉低SCK,完成一个位的传输。

void spi0TransferBit(uint8_t port, uint8_t level)
{
    if(level == 0)
        spiMOSILow(port);
    else
        spiMOSIHigh(port);
    spiCommandWrite(port, 1);
    spiSCKHigh(port);
    spiCommandWrite(port, 1);
    spiMISORead(port);
    spiSCKLow(port);
    spiCommandWrite(port, 1);
}

4.3 spi1TransferBit

和模式0最大的区别是在第二个时钟沿读入MISO

void spi1TransferBit(uint8_t port, uint8_t level)
{
    if(level == 0)
        spiMOSILow(port);
    else
        spiMOSIHigh(port);
    spiCommandWrite(port, 1);
    spiSCKHigh(port);
    spiCommandWrite(port, 1);
    spiSCKLow(port);
    spiCommandWrite(port, 1);
    spiMISORead(port);
}

4.4 spi2TransferBit

和模式0相比,sck方向反过来就是模式2.

void spi2TransferBit(uint8_t port, uint8_t level)
{
    if(level == 0)
        spiMOSILow(port);
    else
        spiMOSIHigh(port);
    spiCommandWrite(port, 1);
    spiSCKLow(port);
    spiCommandWrite(port, 1);
    spiMISORead(port);
    spiSCKHigh(port);
    spiCommandWrite(port, 1);
}

4.5 spi3TransferBit

和模式1相比,sck方向反过来就是模式3

void spi3TransferBit(uint8_t port, uint8_t level)
{
    if(level == 0)
        spiMOSILow(port);
    else
        spiMOSIHigh(port);
    spiCommandWrite(port, 1);
    spiSCKLow(port);
    spiCommandWrite(port, 1);
    spiSCKHigh(port);
    spiCommandWrite(port, 1);
    spiMISORead(port);
}

4.6 写命令序列

最后加一个0x87命令

spi[port].pCommand[spi[port].iCommand++] = 0x87;

同样,判断一下command缓存是否足够

    if(spi[port].iCommand > commandlength)
    {
        printf("spi transfer error: command buffer is overflow %d:%d\n", 
            spi[port].iCommand, commandlength);
        if(spi[port].pCommand)
            free(spi[port].pCommand);
        return -2;
    }

发送command数据

    int writetimeout = spi[port].ftdi->usb_write_timeout;
    spi[port].ftdi->usb_write_timeout = spi[port].iCommand * spi[port].freq;
    int ret = ftdi_write_data(spi[port].ftdi, spi[port].pCommand, spi[port].iCommand);
    if(spi[port].pCommand)
        free(spi[port].pCommand);
    spi[port].ftdi->usb_write_timeout = writetimeout;
    if(ret < 0)
    {
        printf("usb write fail %d\n", ret);
        return -3;
    }

4.7 读数据

读入所有的位数据

    int rdLen = len * 8;
    uint8_t *pReadBuf = (uint8_t *)malloc(rdLen);
    int readtimeout = spi[port].ftdi->usb_read_timeout;
    spi[port].ftdi->usb_read_timeout = spi[port].iCommand + rdLen * spi[port].freq;
    ret = ftdi_read_data(spi[port].ftdi, pReadBuf, rdLen);
    printf("read data number:%d\n", ret);
    if(ret < rdLen)
    {//try again
        printf("retry read:%d\n", ret);
        int remain;
        if(ret < 0)
        {
            remain = rdLen;
            ret = ftdi_read_data(spi[port].ftdi, pReadBuf, rdLen);
        } 
        else
        {
            remain = rdLen - ret;
            ret = ftdi_read_data(spi[port].ftdi, pReadBuf + ret, remain);
        }
        if(ret + remain < rdLen)
        {
            if(pReadBuf)
                free(pReadBuf);
            spi[port].ftdi->usb_read_timeout = readtimeout;
            return -3;
        }
    }
    spi[port].ftdi->usb_read_timeout = readtimeout;

4.8 组合实际数据

因为读入的数据是整个8位数据,所以将对应位的数据组合为8位数据。如果rdBuf是空指针,那么就不需要读数据,这一步就跳过。

    if(rdBuf != NULL)
    {
        int j = 0;
        for(int i = 0; i < rdLen; i++)
        {
            int max = i + 8;
            uint8_t tmp = 0;
            for(; i < max; i++)
            {
                if(spi[port].msb == true)
                {
                    tmp <<= 1;
                    if ((pReadBuf[i] & ((uint8_t)(1 << (spi[port].miso_io1 % 8)))) 
                        == (uint8_t)(1 << (spi[port].miso_io1 % 8)))
                    {
                        tmp |= 0x01;
                    }
                }
                else
                {
                    tmp >>= 1;
                    if ((pReadBuf[i] & ((uint8_t)(1 << (spi[port].miso_io1 % 8)))) 
                        == (uint8_t)(1 << (spi[port].miso_io1 % 8)))
                    {
                        tmp |= 0x80;
                    }
                }
            }
            rdBuf[j++] = tmp;
            i--;
        }
        if(pReadBuf)
            free(pReadBuf);
    }

最后释放缓存,返回即可。

    if(pReadBuf)
        free(pReadBuf);
    printf("SPI Transfer Bytes OK\n");

4. 9 读写函数

可以通过宏定义定义好SPI接口的读写函数

#define spiWriteBytes(port, wrBuf, len)     spiTransferBytes(port, wrBuf, NULL, len)
#define spiReadBytes(port, rdBuf, len)      spiTransferBytes(port, NULL, rdBuf, len)

5. 验证

SPI接口接SPI NOR Flash,其中ADBUS0接MOSI,ADBUS1接MISO,ADBUS2接SCK,ADBUS3接CS。

5.1 初始化FTDI设备模式

    int ret;
    ret = ftdi_set_bitmode(ftdi, 0, BITMODE_MPSSE);
    if(ret < 0)
    {
        printf("Set Mode Fail: %d\n", ret);
        return EXIT_FAILURE;
    }

5.2 初始化GPIO

#define SFLASH_CS_PIN       3
    mpsse_gpio_s gpioSetting;
    gpioSetting.ftdi = ftdi;
    gpioSetting.dir = 0x0000; //All input
    gpioSetting.dir |= ((uint16_t)GPIO_DIR_OUT << spiSetting.sck) 
        | ((uint16_t)GPIO_DIR_OUT << spiSetting.mosi_io0)
        | ((uint16_t)GPIO_DIR_OUT << SFLASH_CS_PIN);        //CS
    gpioSetting.level = 0xFFFF;
    mpsseGpioInit(gpioSetting);

5.3 控制CS

直接使用gpio的api控制CS的高低电平。

#define SFLASH_CS_PIN       3
void sflash0CS(uint8_t port, bool enable)
{
    spiCPOL(port);
    mpsseGpioWrite(SFLASH_CS_PIN, !enable);
}

5.4 读flash的id

    #define CMD_READ_ID             0x9F
    uint8_t cmd[4] = {CMD_READ_ID, 0xff, 0xff, 0xff};
    uint32_t id;

    sflash[port].pCSEnable(port, true);
    spiWriteBytes(sflash[port].spiPort, cmd, 1);
    spiReadBytes(sflash[port].spiPort, cmd + 1, 3);
    sflash[port].pCSEnable(port, false);
    id = ((uint32_t)cmd[1] << 16) | (uint32_t)(cmd[2] << 8) | (uint32_t)(cmd[3]);
    printf("sflash id is:%x\n", id);

5.5 结果

sflash id is:ef4018

这个波形如下图: 

 而最大频率大概为450KHz,有点低

如果需要更高频率,应该要采用官方的方式,固定SCK,MISO和MOSI的方式。

还有一种优化方式,就是将所有的命令一次发出,例如pCSEnable + spiWriteBytes + spiReadBytes + pCSEnable这4个的IO控制序列组合起来,一次写给设备。例如:

    sflash[port].pCSEnable(port, true);
    //spiWriteBytes(sflash[port].spiPort, cmd, 1);
    //spiReadBytes(sflash[port].spiPort, cmd + 1, 3);
    spiTransferBytes(sflash[port].spiPort, cmd, cmd, 4);
    sflash[port].pCSEnable(port, false);

它的波形就变为:

  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值