1.开发环境
操作系统:SylixOS
编程环境:RealEvo-IDE3.0
开发板:MDK972
Nor Flash:EN25Q128
2.原理概述
NOR FLASH是一种常见的存储芯片,数据掉电不会丢失,支持片内执行,因此在嵌入式系统中,常作为启动程序的存储介质。根据传输的数据位,Nor Flash可分为并行Nor(Parallel)Flash和串行(SPI)Flash。SPI Nor Flash较Parallel Flash便宜,接口简单,但速度慢。本文介绍的是SylixOS下基于NUC970的SPI Flash驱动。
通过读取或配置Nor Flash的状态寄存器,可以获得或改变Flash的当前状态,每一位的含义如图 2-1所示。
图 2-1 SPI Flash状态寄存器
根据SPI的传输方式,常见的指令可分为标准SPI指令、Dual SPI指令和Quad SPI指令。常见的指令如图 2-2、图 2-3所示。
图 2-2 SPI Flash读指令
图 2-3 SPI Flash常见指令
3.技术实现
SPI Flash的驱动主要实现对Flash的擦除、读和写功能,并根据具体硬件填充相应信息后挂载YAFFS文件系统即可。其代码框架如程序清单 3-1所示。
程序清单 3-1挂载文件系统
pNor->nor_base = NOR_BASE_ADDR;
pNor->block_size = NOR_ERASE_SIZE;
pNor->chunk_size = 512 + 16;
pNor->bytes_per_chunk = 512;
pNor->spare_per_chunk = 16;
pNor->chunk_per_block = pNor->block_size / pNor->chunk_size;
pNor->block_per_chip = NOR_BLOCK_PER_CHIP;
pNor->nor_erase_fn = __spiNorErase;
pNor->nor_read_fn = __spiNorRead;
pNor->nor_write_fn = __spiNorWrite;
pNor->nor_initialise_fn = __spiNorInitialise;
pNor->nor_deinitialise_fn = __spiNorDeinitialise;
pParam->name = pcName;
pParam->total_bytes_per_chunk = pNor->bytes_per_chunk;
pParam->chunks_per_block = pNor->chunk_per_block;
pParam->n_reserved_blocks = 4;
pParam->start_block = (ulOffsetBytes / NOR_BLOCK_SIZE) + 1;
pParam->end_block = NOR_BLOCK_PER_CHIP - 1;
pParam->use_nand_ecc = 0;
pParam->disable_soft_del = 1;
pParam->n_caches = 10;
pDrv->drv_write_chunk_fn = ynorWriteChunk;
pDrv->drv_read_chunk_fn = ynorReadChunk;
pDrv->drv_erase_fn = ynorEraseBlock;
pDrv->drv_initialise_fn = ynorInitialise;
pDrv->drv_deinitialise_fn = ynorDeinitialise;
pDev->driver_context = &pNorDev;
yaffs_add_device(pDev); /* 增加yaffs设备 */
yaffs_mount(pcName); /* 挂载yaffs文件系统 */
3.1创建SPI设备
根据硬件上SPI Flash挂载的SPI总线创建SPI设备,并设置为模式0。具体实现如程序清单 3-2所示
程序清单 3-2创建设备
pSpi_Nor->Spi_NorDev = API_SpiDeviceCreate(pSpiBusName, SPI_NOR_DEVNAME);
iError = API_SpiDeviceBusRequest(pSpi_Nor->Spi_NorDev); /* 申请SPI总线 */
if (iError == PX_ERROR) {
NOR_DEBUG("Spi Request Bus error\n");
return (PX_ERROR);
}
API_SpiDeviceCtl(pSpi_Nor->Spi_NorDev, SPI_MODE_SET, SPI_MODE_0); /* 设置SPI模式为MODE 0 */
API_SpiDeviceBusRelease(pSpi_Nor->Spi_NorDev); /* 释放SPI总线 */
3.2申请片选线
申请相应的GPIO作为片选线,并默认拉高,在后续操作时可通过调用操作系统GPIO相关的API接口改变片选线状态。具体实现如程序清单 3-3所示。
程序清单 3-3 GPIO初始化
static INT __ssGpioInit (UINT uiSSPin)
{
INT iRet;
iRet = API_GpioRequestOne(uiSSPin, LW_GPIOF_OUT_INIT_HIGH, "SS0"); /* 申请片选线并默认拉高 */
if (iRet < 0) {
printf("API_GpioRequestOne error \n");
}
_G_spiNorObj.uiSSPin = uiSSPin;
return (ERROR_NONE);
}
3.3擦除功能
Nor Flash在写操作前大多需要先擦除,常见的擦除指令可擦除4K、64K以及整片擦除。本例采用4K擦除,命令为20h,其时序如图 3-1所示。
图 3-1 SPI Flash擦除时序
擦除操作属于写操作,因此在擦除之前需要设置状态寄存器写使能位,并等待写操作完成,擦除之后也需要等待擦除操作完成。代码实现如程序清单 3-4所示。
程序清单 3-4擦除函数
static INT __flashErase (ULONG uladdr, UINT uiBlockSize)
{
INT iError;
UINT8 ucTxCmd[4];
PSPI_NOR_OBJ pSpi_Nor = &_G_spiNorObj;
LW_SPI_MESSAGE spiCmdMessage = {
.SPIMSG_uiLen = 4,
.SPIMSG_pucWrBuffer = ucTxCmd,
.SPIMSG_pucRdBuffer = NULL,
};
ucTxCmd[0] = ERASE_4K;
ucTxCmd[1] = uladdr >> 16;
ucTxCmd[2] = uladdr >> 8;
ucTxCmd[3] = uladdr >> 0;
iError = API_SpiDeviceBusRequest(pSpi_Nor->Spi_NorDev); /* 申请SPI总线 */
if (iError == PX_ERROR) {
NOR_DEBUG("Spi Request Bus error\n");
return (PX_ERROR);
}
__flashWriteEnable(); /* 写使能 */
__flashWaitForIdle();
SPI_ENABLE_SS();
API_SpiDeviceTransfer(_G_spiNorObj.Spi_NorDev, &spiCmdMessage, 1); /* 开始擦除 */
SPI_DISABLE_SS();
__flashWaitForIdle();
API_SpiDeviceBusRelease(pSpi_Nor->Spi_NorDev); /* 释放SPI总线 */
return (ERROR_NONE);
}
3.4写功能
根据不同的模式,SPI Flash有多种写指令,其写速度也相应不同。本例使用标准SPI指令02h,其时序如图 3-2所示。
图 3-2 SPI Flash写时序
与擦除相同,在写之前需要写使能,写之后需要等待写操作完成,其代码实现如程序清单 3-5所示。
程序清单 3-5写函数
static INT __pageWrite(const VOID *pvdata, ULONG ulAddr, UINT uiLen)
{
PSPI_NOR_OBJ pSpi_Nor = &_G_spiNorObj;;
UCHAR *pucBuf = (UINT8 *)pvdata;
INT32 iRemainLen = uiLen;
UINT8 ucTxCmd[4];
INT iError;
LW_SPI_MESSAGE spiCmdMessage = {
.SPIMSG_uiLen = 4,
.SPIMSG_pucWrBuffer = ucTxCmd,
.SPIMSG_pucRdBuffer = NULL,
};
ucTxCmd[0] = PAGE_PRO;
ucTxCmd[1] = ulAddr >> 16;
ucTxCmd[2] = ulAddr >> 8;
ucTxCmd[3] = ulAddr >> 0;
LW_SPI_MESSAGE spiRdMessage = {
.SPIMSG_uiLen = iRemainLen,
.SPIMSG_pucWrBuffer = pucBuf,
.SPIMSG_pucRdBuffer = NULL,
};
iError = API_SpiDeviceBusRequest(pSpi_Nor->Spi_NorDev); /* 申请SPI总线 */
if (iError == PX_ERROR) {
NOR_DEBUG("Spi Request Bus error\n");
return (PX_ERROR);
}
__flashWriteEnable(); /* 写使能 */
__flashWaitForIdle();
SPI_ENABLE_SS();
API_SpiDeviceTransfer(_G_spiNorObj.Spi_NorDev, &spiCmdMessage, 1); /* 发送写指令和地址 */
API_SpiDeviceTransfer(_G_spiNorObj.Spi_NorDev, &spiRdMessage, 1); /* 进行数据传输 */
SPI_DISABLE_SS();
SPI_ENABLE_SS();
WRITE_DISABLE(); /* 关闭写使能 */
SPI_DISABLE_SS();
__flashWaitForIdle();
API_SpiDeviceBusRelease(pSpi_Nor->Spi_NorDev); /* 释放SPI总线 */
return (ERROR_NONE);
}
3.5读操作
与写操作相同,在不同的SPI模式下,读操作有不同的指令,读速度也不同。本例采用标准SPI指令03h,其时序图如图 3-3所示。
图 3-3 SPI Flash读时序
具体代码实现如程序清单 3-6所示。
程序清单 3-6读函数
static INT __flashRead (ULONG ulAddr, UINT uiLen, VOID *pvData)
{
INT iError;
UINT8 ucTxCmd[4];
PSPI_NOR_OBJ pSpi_Nor = &_G_spiNorObj;
LW_SPI_MESSAGE spiCmdMessage = {
.SPIMSG_uiLen = 4,
.SPIMSG_pucWrBuffer = ucTxCmd,
.SPIMSG_pucRdBuffer = NULL,
};
ucTxCmd[0] = READ;
ucTxCmd[1] = ulAddr >> 16;
ucTxCmd[2] = ulAddr >> 8;
ucTxCmd[3] = ulAddr >> 0;
LW_SPI_MESSAGE spiRdMessage = {
.SPIMSG_uiLen = uiLen,
.SPIMSG_pucWrBuffer = NULL,
.SPIMSG_pucRdBuffer = pvData,
};
iError = API_SpiDeviceBusRequest(pSpi_Nor->Spi_NorDev); /* 申请SPI总线 */
if (iError == PX_ERROR) {
NOR_DEBUG("Spi Request Bus error\n");
return (PX_ERROR);
}
SPI_ENABLE_SS();
API_SpiDeviceTransfer(_G_spiNorObj.Spi_NorDev, &spiCmdMessage, 1); /* 发送读指令和地址 */
API_SpiDeviceTransfer(_G_spiNorObj.Spi_NorDev, &spiRdMessage, 1); /* 进行数据传输 */
SPI_DISABLE_SS();
API_SpiDeviceBusRelease(pSpi_Nor->Spi_NorDev); /* 释放SPI总线 */
return (ERROR_NONE);
}
4.参考资料
《EN25Q128》