1. 理解SPI总线原理
2. 强化按时序图编程
3. 掌握FLASH
SPI(Serial Peripheral Interface)串行外设接口,是Motorola公司推出的一种同步串行接口技术。具体高速、全双工、同步的特点。总线本身并没有提供流控、应答确认和校验机制,需要特别注意。
SPI至少需要4根线,分别是
从上面,可以理解SPI的收发其实就是在时钟信号下采样过程,那么我们是在时钟的上升沿、下降沿、高电平、低电平采样呢?SPI按时钟极性(CPOL)和时钟相位(CPHA)分成4种模式用于控制时钟采样,通信双方模式必须一致,具体如下
模式 | CPOL | CPHA |
---|---|---|
Mode0 | 0 | 0 |
Mode1 | 0 | 1 |
Mode2 | 1 | 0 |
Mode3 | 1 | 1 |
时钟极性CPOL是用来配置SCLK的电平出于哪种状态时是空闲态或者有效态,时钟相位CPHA
是用来配置在第几个边沿采样数据。
CPOL=0,表示当SCLK=0时处于空闲态,所以有效状态就是SCLK处于高电平时
CPOL=1,表示当SCLK=1时处于空闲态,所以有效状态就是SCLK处于低电平时
CPHA=0,表示数据采样是在第1个边沿,数据发送在第2个边沿
CPHA=1,表示数据采样是在第2个边沿,数据发送在第1个边沿
主机和从机的发送数据是同时完成的,两者的接收数据也是同时完成的。所以为了保证主从机正确通信,应使得它们的SPI具有相同的时钟极性和时钟相位。
SPI协议并没有规定数据一定是8位的,也可以是16位的,要看通信双方支持哪种。
传输时是高位先传还是低位先传,协议也没强制要求,也要看双方支持。
至此,SPI协议部分已经全部介绍完了,可见SPI非常的简洁高效,且弹性很大,需要结合具体的应用配置。
GD25Q40芯片是一款Nor FLash,Nor Flash的特点是以‘块’为操作的最小擦写单元,字节为最小读取单元。例如,该款芯片有512KByte=4094Kbit=4MBit,每页大小256Byte,每页就是Nand Flash的最下操作‘块’,具体如图
而且需要遵循先擦后写的操作。flash进行写操作时,只能将相应的位由1变0,而擦除才能把块内所有位由0变1。所有写入数据时,如果该页已经存在数据,必须先擦除再写。
既然Flash有如此特性,芯片是如何管理该芯片的呢?如下图,芯片通过一个命令和逻辑控制器与真正的存储空间进行交互,也可以这么理解,外部MCU要通过SPI总线,向FLASH芯片发送各式各样的命令来控制芯片的读写擦操作。
具体命令如下,命令很多,初看有点眼花,可以先看我标红的几个命令,可以看出,命令无非是写使能/去使能,读芯片状态,读数据,写数据,擦除命令。再仔细看其他命令也无法这几类,只是在不同模式的下操作罢了。注:此处提到的模式时芯片提供的,例如快速读写模式等等,有兴趣可阅读芯片手册,我选的是标准模式。
最后,只要安装命令的时序要求写成代码就可以了,具体时序在下面说。
用SPI总线读写FLASH芯片是非常常见的应用
GD32是支持SPI控制器的,为了加深对SPI协议的理解,我会软件模拟一个SPI,也会用SPI控制器来实现一个,毕竟在实际应用中多少用硬件SPI控制器的。
从手册里可以查到,支持mode 0和3,此处我选mode 0,即CPOL和CPHA都为0。
参考上表,我把延时时间固定5us,这样代码会简单很多,且满足了上述ns级最小延时的要求,虽然有几项有最大时间要求,却不影响我们编码。代码如下:
U8 DRV_SPI_SwapByte(IN U8 byte)
{
U8 i = 0;
U8 inDate = byte;
U8 outBit = 0;
U8 outDate = 0;
/* SCKPL = 0; SCKPH = 0 */
for (i = 0; i < 8; i++)
{
if (inDate & 0x80)
{
SPI_MOSI_HIGH;
}
else
{
SPI_MOSI_LOW;
}
SPI_Delay(5);
SPI_SCK_HIGH;
outBit = SPI_MISO_READ;
if (outBit)
{
outDate |= 0x1;
}
SPI_Delay(5);
SPI_SCK_LOW;
SPI_Delay(5);
inDate <<= 1;
if (i <7)
{
outDate <<= 1;
}
}
return outDate;
}
详细阅读SPI配置部分代码,与前面原理部分说的完全一致,且参数GD32手册里也有详细说明。
U8 DRV_SPI_SwapByte(IN U8 byte)
{
while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_TBE) == RESET);
SPI_I2S_SendData(SPI1, byte);
while (SPI_I2S_GetBitState(SPI1, SPI_FLAG_RBNE) == RESET);
return SPI_I2S_ReceiveData(SPI1);
}
static VOID SPI_Configuration(VOID)
{
SPI_InitPara SPI_InitStructure;
RCC_APB2PeriphClock_Enable(RCC_APB2PERIPH_SPI1, ENABLE);
SPI_InitStructure.SPI_TransType = SPI_TRANSTYPE_FULLDUPLEX;
SPI_InitStructure.SPI_Mode = SPI_MODE_MASTER;
SPI_InitStructure.SPI_FrameFormat = SPI_FRAMEFORMAT_8BIT;
SPI_InitStructure.SPI_SCKPL = SPI_SCKPL_LOW;
SPI_InitStructure.SPI_SCKPH = SPI_SCKPH_1EDGE;
SPI_InitStructure.SPI_SWNSSEN = SPI_SWNSS_SOFT;
SPI_InitStructure.SPI_PSC = SPI_PSC_32;
SPI_InitStructure.SPI_FirstBit = SPI_FIRSTBIT_MSB;
SPI_InitStructure.SPI_CRCPOL = 7;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Enable(SPI1, ENABLE);
}
写使能,图中1,2,3步所示,代码也是按这个时序写的,下面的命令就不再画图说明了
static VOID GD25Q40_WriteEnable(VOID)
{
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(WREN);
GD25Q40_CS_HIGH();
}
static VOID GD25Q40_WaitForWriteEnd(VOID)
{
U8 FLASH_Status = 0;
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(RDSR);
do
{
FLASH_Status = DRV_SPI_SwapByte(Dummy_Byte);
}
while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress */
GD25Q40_CS_HIGH();
}
Sector擦除,擦除可以理解为一个特殊的写过程,即写FF的过程,所以在下一个操作之前需要等待写完成
VOID DRV_GD25Q40_SectorErase(U32 SectorAddr)
{
GD25Q40_WriteEnable();
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(SE);
DRV_SPI_SwapByte((SectorAddr & 0xFF0000) >> 16);
DRV_SPI_SwapByte((SectorAddr & 0xFF00) >> 8);
DRV_SPI_SwapByte(SectorAddr & 0xFF);
GD25Q40_CS_HIGH();
GD25Q40_WaitForWriteEnd();
}
VOID DRV_GD25Q40_BulkErase(VOID)
{
GD25Q40_WriteEnable();
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(BE);
GD25Q40_CS_HIGH();
GD25Q40_WaitForWriteEnd();
}
VOID DRV_GD25Q40_BufferRead(U8* pBuffer, U32 ReadAddr, U16 NumByteToRead)
{
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(READ);
DRV_SPI_SwapByte((ReadAddr & 0xFF0000) >> 16);
DRV_SPI_SwapByte((ReadAddr& 0xFF00) >> 8);
DRV_SPI_SwapByte(ReadAddr & 0xFF);
while (NumByteToRead--)
{
*pBuffer = DRV_SPI_SwapByte(Dummy_Byte);
pBuffer++;
}
GD25Q40_CS_HIGH();
}
VOID DRV_GD25Q40_PageWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite)
{
GD25Q40_WriteEnable();
GD25Q40_CS_LOW();
DRV_SPI_SwapByte(WRITE);
DRV_SPI_SwapByte((WriteAddr & 0xFF0000) >> 16);
DRV_SPI_SwapByte((WriteAddr & 0xFF00) >> 8);
DRV_SPI_SwapByte(WriteAddr & 0xFF);
while (NumByteToWrite--)
{
DRV_SPI_SwapByte(*pBuffer);
pBuffer++;
}
GD25Q40_CS_HIGH();
GD25Q40_WaitForWriteEnd();
}
写入一段buf,该接口是在按页的基础上,增加是否换页写的封装接口
VOID DRV_GD25Q40_BufferWrite(U8* pBuffer, U32 WriteAddr, U16 NumByteToWrite)
{
U8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
Addr = WriteAddr % GD25Q40_PageSize;
count = GD25Q40_PageSize - Addr;
NumOfPage = NumByteToWrite / GD25Q40_PageSize;
NumOfSingle = NumByteToWrite % GD25Q40_PageSize;
/* WriteAddr is GD25Q40_PageSize aligned */
if (Addr == 0)
{
/* NumByteToWrite < GD25Q40_PageSize */
if (NumOfPage == 0)
{
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > GD25Q40_PageSize */
{
while (NumOfPage--)
{
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);
WriteAddr += GD25Q40_PageSize;
pBuffer += GD25Q40_PageSize;
}
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
else /* WriteAddr is not GD25Q40_PageSize aligned */
{
if (NumOfPage == 0)
{
/* (NumByteToWrite + WriteAddr) > GD25Q40_PageSize */
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, temp);
}
else
{
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* NumByteToWrite > GD25Q40_PageSize */
{
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / GD25Q40_PageSize;
NumOfSingle = NumByteToWrite % GD25Q40_PageSize;
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
while (NumOfPage--)
{
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, GD25Q40_PageSize);
WriteAddr += GD25Q40_PageSize;
pBuffer += GD25Q40_PageSize;
}
if (NumOfSingle != 0)
{
DRV_GD25Q40_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
VOID APP_SPI_Test(VOID)
{
U32 Manufact_ID = 0;
U8 Tx_Buffer[256];
U8 Rx_Buffer[256];
U16 i = 0;
DRV_GD25Q40_Init();
printf("\n\rGD32103C-EVAL-V1.1 SPI Flash: configured...\n\r");
Manufact_ID = DRV_GD25Q40_ReadID();
printf("\n\rThe Flash_ID:0x%X\n\r", Manufact_ID);
if (Manufact_ID == sFLASH_ID)
{
printf("\n\rWrite to Tx_Buffer:\n\r");
for(i=0; i<=255; i++)
{
Tx_Buffer[i] = i;
printf("0x%02X ",Tx_Buffer[i]);
if(i%16 == 15)
{
printf("\n\r");
}
}
printf("\n\rRead from Rx_Buffer:\n\r");
DRV_GD25Q40_SectorErase(FLASH_WriteAddress);
DRV_GD25Q40_BufferWrite(Tx_Buffer,FLASH_WriteAddress, 256);
APP_Delay(10);
DRV_GD25Q40_BufferRead(Rx_Buffer,FLASH_ReadAddress, 256);
for(i=0; i<=255; i++)
{
printf("0x%02X ", Rx_Buffer[i]);
if(i%16 == 15)
{
printf("\n\r");
}
}
printf("\n\rSPI Flash: Initialize Successfully!\n\r");
}
else
{
printf("\n\rSPI Flash: Initialize Fail!\n\r");
}
}
https://github.com/YaFood/GD32F103/tree/master/TestSPI