目录
QSPI采用4根线为数据口,SCK和CS保留同样的功能,一般4个数据线采用MSB的方式发送数据,即高位在前。
QSPI只能是半双工工作。
1. 写
int qspiWriteBytes(uint8_t port, uint8_t* wrBuf, uint16_t len)
命令缓存的大小小于SPI的方式
int commandlength = size * 2 * 3 * 3 * spi[port].freq + 1 + 3 + 3;
在写之前把4个数据口设置为输出
gpio.dir |= ((uint16_t)1 << spi[port].mosi_io0) | ((uint16_t)1 << spi[port].miso_io1)
| ((uint16_t)1 << spi[port].io2) | ((uint16_t)1 << spi[port].io3);
QSPI的写和SPI的模式0写类似,区别是只发送,不需要读入。最大的区别是每个字节的发送
while(size > 0)
{
int i;
uint8_t wrDat;
wrDat = wrBuf[j];
for(i = 0; i < 2; i++)
{
if(wrDat & (1 << 4))
gpio.level |= ((uint16_t)1 << spi[port].mosi_io0);
else
gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].mosi_io0));
if(wrDat & (1 << 5))
gpio.level |= ((uint16_t)1 << spi[port].miso_io1);
else
gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].miso_io1));
if(wrDat & (1 << 6))
gpio.level |= ((uint16_t)1 << spi[port].io2);
else
gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].io2));
if(wrDat & (1 << 7))
gpio.level |= ((uint16_t)1 << spi[port].io3);
else
gpio.level &= (uint16_t)(~((uint16_t)1 << spi[port].io3));
wrDat <<= 4;
spiCommandWrite(port, 1);
spiSCKHigh(port);
spiCommandWrite(port, 1);
spiSCKLow(port);
spiCommandWrite(port, 1);
}
j++;
size--;
}
写完后要把gpio的方向改回来
gpio.dir &= (uint16_t)(~(((uint16_t)1 << spi[port].miso_io1)
| ((uint16_t)1 << spi[port].io2) | ((uint16_t)1 << spi[port].io3)));
spiCommandWrite(port, 1);
2. 读
int qspiReadBytes(uint8_t port, uint8_t* rdBuf, uint16_t len)
同样在读之前设置4个io为输入
gpio.dir &= (uint16_t)(~(((uint16_t)1 << spi[port].mosi_io0) | ((uint16_t)1 << spi[port].miso_io1)
| ((uint16_t)1 << spi[port].io2) | ((uint16_t)1 << spi[port].io3)));
命令缓存拼接:
int size = len * 2;
int commandlength = size * (2 * 3 * spi[port].freq + 1) + 3 + 1 + 3;
spi[port].pCommand = (uint8_t *)malloc(commandlength);
spi[port].iCommand = 0;
gpio.dir &= (uint16_t)(~(((uint16_t)1 << spi[port].mosi_io0) | ((uint16_t)1 << spi[port].miso_io1)
| ((uint16_t)1 << spi[port].io2) | ((uint16_t)1 << spi[port].io3)));
while(size > 0)
{
spiSCKHigh(port);
spiCommandWrite(port, 1);
spi[port].pCommand[spi[port].iCommand++] = gpioReadCommand[gpioCommand(port)];
spiSCKLow(port);
spiCommandWrite(port, 1);
size--;
}
同样将gpio的方向改为默认
gpio.dir |= (uint16_t)1 << spi[port].mosi_io0; //mosi output
spiCommandWrite(port, 1);
发送完命令缓存后,需要读回数据
int rdLen = len * 2;
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;
printf("spi transfer read fail\n");
return -4;
}
}
spi[port].ftdi->usb_read_timeout = readtimeout;
对读回来的数据进行拼接
if(rdBuf != NULL)
{
int j = 0;
for(int i = 0; i < rdLen; i++)
{
int max = i + 2;
uint8_t tmp = 0;
for(; i < max; i++)
{
if(rdBuf[i] & ((uint8_t)1 << (spi[port].mosi_io0 % 8)))
tmp |= 0x01;
if(rdBuf[i] & ((uint8_t)1 << (spi[port].miso_io1 % 8)))
tmp |= 0x02;
if(rdBuf[i] & ((uint8_t)1 << (spi[port].io2 % 8)))
tmp |= 0x04;
if(rdBuf[i] & ((uint8_t)1 << (spi[port].io3 % 8)))
tmp |= 0x08;
tmp <<= 4;
}
rdBuf[j++] = tmp;
i--;
}
if(pReadBuf)
free(pReadBuf);
}
3. 验证
基于SPI Nor Flash的验证方式,增加io2和io3的初始化
spiSetting.io2 = 4;
spiSetting.io3 = 5;
将SFlash的io类型改为QSPI
sflashSetting.ioType = SFLASH_QSPI;//SFLASH_SPI;
增加计算时间差
struct timespec start, end;
long long diff;
clock_gettime(CLOCK_REALTIME, &start);
#if (EX_SFLASH_SIZE < 4096 * 10)
for(i = EX_SFALSH_ADDR / 4096; i < (EX_SFALSH_ADDR + EX_SFLASH_SIZE + 4095) / 4096; i++)
{
sflashEraseSector(port, i * 4096);
}
#else
sflashEraseChip(port);
#endif
clock_gettime(CLOCK_REALTIME, &end);
diff = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
diff /= 1000000;
printf("erase time:%dms\n", (int)diff);
clock_gettime(CLOCK_REALTIME, &start);
sflashWrite(port, EX_SFALSH_ADDR, wrBuf, EX_SFLASH_SIZE);
clock_gettime(CLOCK_REALTIME, &end);
diff = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
diff /= 1000000;
printf("write time:%dms\n", (int)diff);
clock_gettime(CLOCK_REALTIME, &start);
sflashRead(port, EX_SFALSH_ADDR, rdBuf, EX_SFLASH_SIZE);
clock_gettime(CLOCK_REALTIME, &end);
diff = (end.tv_sec - start.tv_sec) * 1000000000LL + (end.tv_nsec - start.tv_nsec);
diff /= 1000000;
printf("read time:%dms\n", (int)diff);
for(i = 0; i < EX_SFLASH_SIZE; i++)
{
if(wrBuf[i] != rdBuf[i])
//if(rdBuf[i] != 0xff)
{
printf("sflash test fail %d: %x!=%x\n", i, wrBuf[i], rdBuf[i]);
break;
}
}
printf("sflash test finish\n");
设置1MB的读写数据,验证结果如下:
Open device OK: 0
sflash Winbond
sflash size 16MB, 24bits address
status:3e
sflash id is:ef4018
Erase Chip Finish
erase time:41035ms
write time:16402ms
read time:4180ms
sflash test finish
这里读是体现QSPI速度的,1MB/0.418 = 2.4MB/s。
可以反过来看一下SPI的速度。sflashSetting.ioType改为SPI。
erase time:42027ms
write time:33931ms
read time:21780ms
速度是:1MB/2.178 = 470KB/s。
可以看出模拟SPI的方式速度有点慢,看样子还是要采用官方的方式来实现SPI的时序。