一、编程要点

1、 3字节地址模式和4字节地址模式

W25Q256的容量有256M-bits,上图可见它的终止地址是1FFFFFFh,每个地址存一个字节。256Mbits/8 = 32MB。32MB * 2^(20) = 33554432 = 2000000h。所以地址的范围是0h ~ 1FF_FFFFh。
对于128Mbits的flash来说,地址范围是0h ~ FF_FFFFh。
3字节地址,可寻址范围为,2^(24),正好是到FF_FFFFh。4字节地址寻址范围增加了100_0000h ~ FFFF_FFFFh(总共4GB)。

2、Flash芯片的特点


图片来自野火教学,W25Q256属于NORFLASH。

3、SPI_FLASH的一些重要寄存器以及如何编程控制

(1) SR1的BUSY位

WEL和BUSY的功能好像重合了?WEL是写使能标志位,执行写使能指令后置1,为0禁止写入。清0方法(指令):写禁用、写入、擦除、写SR、擦除安全R、写入安全SR。
BUSY是忙标志位,置1期间,忽略(读SR、Erase/Program Suspend instruction除外)所有指令。置1方法(指令),写入、擦除、写SR、擦写安全SR。
还是有不同的,读这两个位置,都可以判断是否空闲。

读取这个寄存器的方法,实际上也是靠MCU发送给W25的指令实现的。下面就是读取这个BUSY位的方法 内联代码片

void SPI_FLASH_WaitForWriteEnd( void )
{UINT8 FLASH_Status = 0;SPI_CS_L();SPI_FLASH_SendByte( W25X_ReadStatusReg );do{FLASH_Status = SPI_FLASH_SendByte( Dummy_Byte ); //读取FLASH芯片的状态寄存器}while ( ( FLASH_Status & 0x01 ) == 1 ); //正在写入标志SPI_CS_H();
}

(2) SR3的ADS位

发送B7指令进入4-Byte Address Mode。退出是E9。
实际上不确定需不需要确认进入了4字节地址模式,但稳妥器件还是写了确认代码。 内联代码片

void SPI_FLASH_WaitFor4BMode( void )
{UINT8 FLASH_Status3 = 0;SPI_CS_L();SPI_FLASH_SendByte( W25X_ReadStatusReg3 );do{FLASH_Status3 = SPI_FLASH_SendByte( Dummy_Byte ); //读取FLASH芯片的状态寄存器3}while ( ( FLASH_Status3 & 0x01 ) == 0 ); //3地址模式标志SPI_CS_H();
}

(3) 其他的一些关键指令

i 写使能指令

写入、擦除、写状态寄存器(这个目前还不清楚怎么实现,暂时也用不到,之前以为通过发指令改变只读标志位就是写了,现在想想错了)之前都是需要发送写使能指令的。

ii 需要结尾读BUSY位的操作

擦除、写入

iii 需要结尾拉高CS电平的操作

所有指令都需要,因为所有指令都是靠拉高电平结束的。读取ID、写使能、进入4字节地址模式、退出4字节地址模式、确认BUSY的函数(这里面发送了读取SR的指令)、写入指令(写入都指pageprogram)、读取指令。这里说一下擦除,前面说了擦除后面要接读BUSY位的操作,CS_H的操作要放它前面。再说一下读SR,只有读到想要的值才罢休,所以CS_H放判断标志位的语句后面,再结束读SR指令,代码看前面。

二、代码设计

1、读取ID

这个是最简单的,也是最重要的,因为读取到了ID,就说明能够成功发数以及收数了。
先看芯片手册的时序图,发送一个指令,然后在连续发送三个DUMMY字节就,在发送三个DUMMY字节的同时,W25向MCU返回ID。需要注意的是,就算主机只想收数,也需要向W25发数,因为SPI的时钟是靠主机驱动的,主机发数才有时钟信号(实际上没找到手册哪句话说只有主机发数才有时钟,但是实验做下来是这样的)。

UINT32 SPI_FLASH_ReadID( void )
{UINT32 JedecDeviceID = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;SPI_CS_L();SPI_FLASH_SendByte( W25X_JedecDeviceID );Temp0 = SPI_FLASH_SendByte( Dummy_Byte );Temp1 = SPI_FLASH_SendByte( Dummy_Byte );Temp2 = SPI_FLASH_SendByte( Dummy_Byte );JedecDeviceID = ( Temp0 << 16 ) | ( Temp1 << 8 ) | Temp2;SPI_CS_H();return JedecDeviceID;}

定义为UINT32的变量 才能保证收到24位的ID。分三次收完,然后再拼接即可。要注意的是,这里我们W25手册里面规定了MSB模式,所以高位在前,先接收高位ID,勿搞错顺序。

2、写使能

void SPI_FLASH_WriteEnable( void )
{SPI_CS_L();SPI_FLASH_SendByte( W25X_WriteEnable ); //发送写使能指令SPI_CS_H();
}

简单,记得拉高电平,进入离开4字节模式指令差不多。像这里我们只关注发送,就无须创建一个变量来接收了。

3、等待忙结束和等待进入4字节模式完成

void SPI_FLASH_WaitForWriteEnd( void )
{UINT8 FLASH_Status = 0;SPI_CS_L();SPI_FLASH_SendByte( W25X_ReadStatusReg );do{FLASH_Status = SPI_FLASH_SendByte( Dummy_Byte ); //读取FLASH芯片的状态寄存器}while ( ( FLASH_Status & 0x01 ) == 1 ); //正在写入标志SPI_CS_H();
}

这个写法很巧妙,发送读SR指令和发送DUMMY来接收SR的值的语句是连续的。BUSY标志位为0退出这个循环。

void SPI_FLASH_WaitFor4BMode( void )
{UINT8 FLASH_Status3 = 0;SPI_CS_L();SPI_FLASH_SendByte( W25X_ReadStatusReg3 );do{FLASH_Status3 = SPI_FLASH_SendByte( Dummy_Byte ); //读取FLASH芯片的状态寄存器3}while ( ( FLASH_Status3 & 0x01 ) == 0 ); //3地址模式标志SPI_CS_H();
}

为了确认进入了4地址模式,实际上清楚道这个函数有没有必要,因为发送Enter4Bmode指令完成基本就无碍了。

4、擦除

void SPI_FLASH_SectorErase4B( UINT32 SectorAddr )
{SPI_FLASH_WriteEnable();SPI_CS_L();SPI_FLASH_SendByte( W25X_SectorErase );SPI_FLASH_SendByte( ( SectorAddr & 0xFF000000 ) >> 24 ); //发送擦除扇区地址的高位SPI_FLASH_SendByte( ( SectorAddr & 0xFF0000 ) >> 16 ); //发送擦除扇区地址的次高位SPI_FLASH_SendByte( ( SectorAddr & 0xFF00 ) >> 8 ); //发送擦除扇区地址中位SPI_FLASH_SendByte( SectorAddr & 0xFF ); //发送擦除扇区地址的低位SPI_CS_H();SPI_FLASH_WaitForWriteEnd(); //等待擦除完毕
}

要注意的是4BMode的擦除指令和3BMode指令是不一样的,擦除不同大小的指令也不同。分为扇区擦除(4KB,pageprogram的1page是256B)、块擦除(一般认为块是64KB的,前面的地址表也是一64KB为准的)、和整块芯片擦除(这个时间长,不建议轻易用)。

5、Read Data函数

上面高亮的话,表明读取函数是不需要考虑对齐的,只需要一直给时钟,然后一直读取就行。下面给出四字节地址模式的读取函数。

void SPI_FLASH_BufferRead4B( UINT8* pBuffer, UINT32 ReadAddr, UINT16 NumByteToRead )
{SPI_CS_L();SPI_FLASH_SendByte( W25X_ReadData4B );SPI_FLASH_SendByte( ( ReadAddr & 0xFF000000 ) >> 24 );SPI_FLASH_SendByte( ( ReadAddr & 0xFF0000 ) >> 16 );SPI_FLASH_SendByte( ( ReadAddr & 0xFF00 ) >> 8 );SPI_FLASH_SendByte( ReadAddr & 0xFF );while ( NumByteToRead-- ){*pBuffer = SPI_FLASH_SendByte( Dummy_Byte ); /* 读取一个字节*/pBuffer++;/* 指向下一个字节缓冲区 */}SPI_CS_H();
}

6、PageProgram

(1)页写入函数

下图是芯片手册上的说明。
第二段的表述很重要。
第一句话"If the last address byte is not zero, and the number of clocks exceeds the remianing page length, the addressing will wrap to the beginning of the page.‘’ 其中"the last address byte(the 8 least significant address bits)“的,拿3byteAM来说,地址三个字节,最后一个字节必须00,比如"00 00 00”(0d)、“00 01 00”(256d)、“00 02 00”(512)。实际上就是说地址要对齐到一页也就是256B的整数倍。“the number of clocks cannot exceed the remaining page length”的意思是,NumBytetoWrite(要写入的字节数量)如果超过了count(剩余能写入的地址的数量)加上没对齐,后果是寻址会重定位到页的开头。就比如前面地址0 ~ 127都写入了AA,现往地址127 ~ 255写入256个数据CC。那么从第128个数据CC开始,它去尝试覆盖地址0 ~ 127的数据AA。但是最终结果是地址0 ~ 127都写入了数据88。很奇怪对不对?实际上,可以覆盖写,就是不用擦除也能写,就是只能1变0。不能0变1。比如要用FF覆盖00,这不行,但用00覆盖11,则可以。用CC(1100 1100),去覆盖AA(1010 1010),结果会是88(1000 1000),它尽力了,1该变成0的地方它都变了。0实在变不了1,它也没办法。

第二句话,说明了什么情况下能够写入少于256个字节,而不产生异常(对此页已写入的数据没有任何影响),称之为"a partial page"。其中关于"a partial page"的条件之一是“the number of clocks cannot exceed the remaining page length”,就是说NumByteToWrite不超过count。

最后一句话,"If more than 256 bytes are sent to the device the addressing will wrap to the beginning of the page and overwrite previously sent data."如果编入了超过一页的数据量(地址对齐的情况,地址不齐的情况前面已经说了),那么寻址会回到页的开头,并且,先前发送的数据会被覆盖(注意是发送,它第一步并不会写入)。对此也进行了测试。
往地址0 ~ 256发送数组arr[384],前128个元素是AA,后256个元素是CC,结果是 地址0 ~ 256全被写入数据CC 。这和第一句话的测试不一样,第一句话是尝试覆盖,这里是真能覆盖,因为它就没往地址0 ~ 127写入过数据AA。


下面是4字节地址模式的页写入代码

/**
* @brief 对 FLASH 按页写入数据,调用本函数写入数据前需要先擦除扇区
* @param pBuffer,要写入数据的指针
* @param WriteAddr,写入地址,不需要对其256KB,只需确认目标存储单元是擦除状态即可
* @param NumByteToWrite,写入数据长度,必须小于等于页大小
*/
void SPI_FLASH_PageWrite4B( UINT8* pBuffer, UINT32 WriteAddr, UINT16 NumByteToWrite )
{SPI_FLASH_WriteEnable();SPI_CS_L();SPI_FLASH_SendByte( W25X_PageProgram4B );SPI_FLASH_SendByte( ( WriteAddr & 0xFF000000 ) >> 24 );SPI_FLASH_SendByte( ( WriteAddr & 0xFF0000 ) >> 16 );SPI_FLASH_SendByte( ( WriteAddr & 0xFF00 ) >> 8 ); //发送擦除扇区地址的中位SPI_FLASH_SendByte( WriteAddr & 0xFF ); //发送擦除扇区地址的低位while ( NumByteToWrite-- ){SPI_FLASH_SendByte( *pBuffer ); /* 发送当前要写入的字节数据 */pBuffer++;}SPI_CS_H();SPI_FLASH_WaitForWriteEnd();/* 等待写入完毕*/
}

用指针做形参,实参数组首元素的首地址,

UINT8 writeBuff[530] = {0};
SPI_FLASH_BufferWrite(writeBuff,10,530);

这样,writeBuff实际上是数组首元素的首地址,就等价于&writeBuff[0]。对指针做加减运算它移动的单位并不是1,而是sizeof(指向的类型)。这里指针指向的类型是UINT8,所以pBuffer++后,它指向下一个数组元素。如果传&writeBuff,那么指的就是整个数组了,pBuffer++后,它指向下一个数组,会报错。这里提一下i++,i++返回原来的值,对比++i,++i返回+1后的值。NumByteWrite–到0后,数组的数也能传完。

(2)任意字节,任意地址写入(首先要确保写入的区域是擦除状态)

以页写入函数为基础。
分为四种情况,先说前面两种简单的情况:
1、地址对齐了,写入的数据正好是page的整数倍。
2、地址对齐了,写入的数据不是page的整数倍。

NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;

这里SPI_FLASH_PageSize宏定义为256,NumOfPage是能写完多少完整的页。

NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

NumOfSingle这个就是剩余不满一页要传输的字节个数。

3、地址没对齐,

Addr = WriteAddr % SPI_FLASH_PageSize
count = SPI_FLASH_PageSize - Addr;

第一行求余后,若能整除则地址对齐。若不能,则求余的结果就是本身。count用来记录还差多少字节 能写满一页。
(1)若NumByteToWrite <= count,那么写完就不用管了。
(2)若NumByeToWrite > count,先写满当前页count个位置。接着就用NumByteToWrite - count作为新的NumByteToWrite然后就回到了第1、2种情况,代码复用即可。

void SPI_FLASH_BufferWrite(UINT8* pBuffer, UINT32 WriteAddr, UINT16 NumByteToWrite)
{UINT8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/*mod 运算求余,若 writeAddr 是 SPI_FLASH_PageSize 整数倍0,运算结果 Addr 值为 0*/Addr = WriteAddr % SPI_FLASH_PageSize;/*差 count 个数据值,刚好可以对齐到页地址*/count = SPI_FLASH_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;/*mod 运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* Addr=0,则 WriteAddr 刚好按页对齐 aligned  */if (Addr == 0){/*NumByteToWrite < SPI_FLASH_PageSize*/if (NumOfPage == 0) {SPI_FLASH_PageWrite4B(pBuffer, WriteAddr, NumByteToWrite);}
/*****************************这上面是第1种情况*************************/        else /* NumByteToWrite >= SPI_FLASH_PageSize*/{ /*先把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite4B(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*多余的不满一页的数据,把它写完,NumOfSingle是0的情况下也兼容了,相当于写0个字节进去*/SPI_FLASH_PageWrite4B(pBuffer, WriteAddr, NumOfSingle);}}
/*****************************这上面是第2种情况*************************/   /* 若地址与 SPI_FLASH_PageSize 不对齐 */else {/*当前页的剩余的位置数量(count)能写完要写的字节,正常页写入就好了*/if( count >= NumByteToWrite )SPI_FLASH_PageWrite4B( pBuffer, WriteAddr, NumByteToWrite );/*当前页的剩余的位置数量(count)写不完要写的字节*/else//count < NumByteToWrite/*先写满当前页*/{SPI_FLASH_PageWrite4B( pBuffer, WriteAddr, count );/*第一页写满了,然后计算新的NumByteToWriterite,直接回到1,2种情况,要注意地址要相应地改变*/NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;WriteAddr += count;if ( NumOfPage == 0 ){SPI_FLASH_PageWrite4B( pBuffer, WriteAddr, NumByteToWrite );}else /* NumByteToWrite >= SPI_FLASH_PageSize */{/*先把整数页都写了*/while ( NumOfPage-- ){SPI_FLASH_PageWrite4B( pBuffer, WriteAddr, SPI_FLASH_PageSize );WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*多余的不满一页的数据,把它写完*/SPI_FLASH_PageWrite4B( pBuffer, WriteAddr, NumOfSingle );}}}}

下面测试了最复杂的情况,地址不对齐,要写入的字节也大于第一页count。
1、先擦除一个扇区sector(大小为4KB(4096B)),每个字节对于1个地址,总共对应0~4096个地址。
2、在地址0 ~ 127写入AA,
3、往地址128 ~ 639地址写入512个AB(地址没对齐)。
结果符合预期,表明该代码符合设计要求。
下面附上主函数模块的代码。

UINT8 writeBuff1[128] = {0};
UINT8 writeBuff2[512] = {0};
UINT8 readBuff[4096] = {0};volatile UINT32 DeviceID = 0;
volatile UINT32 FlashID = 0;int main()
{Clk_Init();LED_GPIO_Config();SPI_FLASH_Init();DeviceID = SPI_FLASH_ReadDeviceID();FlashID = SPI_FLASH_ReadID();if ( FlashID == sFLASH_ID ){SPI_FLASH_Enter4ByteMode();SPI_FLASH_WaitFor4BMode();//        SPI_FLASH_ChipErase();SPI_FLASH_SectorErase4B( 0 );int i;for(i=0;i<128;i++){writeBuff1[i]=0xAA;}int j;for(j=0;j<512;j++){writeBuff2[j]=0xAB;}SPI_FLASH_BufferWrite(writeBuff1,0,128);SPI_FLASH_BufferWrite(writeBuff2,128,512);       SPI_FLASH_BufferRead4B( readBuff, 0, 4096 );}

总结

本文记录了nxp公司的kea128芯片 SPI驱动 spi flash的开发流程,主要是对于spi_flash的代码分析,开发工具是IAR。

nxp kea128 spi_flash驱动开发相关推荐

  1. NXP LS1046A及飞腾新四核 FT2004 PCIE EP端LINUX设备驱动开发

    文章目录 前言 一.PCIE 硬件简介 二.PCIE EP地址映射原理介绍 1. PCI总线的各种域(存储器域.PCI总线域) 2. 开发EP设备驱动要做的事 三.NXP LS1046A PCIE E ...

  2. NXP——图形驱动开发实习生

    图形驱动开发实习生 30min 1.自我介绍(中英文) 外企都要准备英文 2.实习时长 项目一 1.问三维图像分割的论文 是第几作者 2.ransac算法详细讲一讲 3.论文中代码是否由C++调用外部 ...

  3. stm32官方例程在哪找_正点原子Linux第十一章模仿STM32驱动开发格式实验

    1)资料下载:点击资料即可下载 2)对正点原子Linux感兴趣的同学可以加群讨论:935446741 3)关注正点原子公众号,获取最新资料更新 第十一章模仿STM32驱动开发格式实验 在上一章使用C语 ...

  4. Linux 中的驱动开发的初学者体会

    Linux 中的驱动开发的初学者体会 很多年前,心里就存下这样一个愿望.就是把Linux 的驱动开发搞清楚. 但是一开始上上这样的开发难度天大了,对着一堆的寄存器发愁. 于是就从简单的STM8,PIC ...

  5. ARM(IMX6U)裸机模仿STM32驱动开发实验(定义外设结构体)

    参考:Linux之ARM(IMX6U)裸机模仿STM32驱动开发格式 作者:一只青木呀 发布时间: 2020-08-15 12:11:56 网址:https://blog.csdn.net/weixi ...

  6. linux驱动开发音频设备驱动,linux驱动开发—基于Device tree机制的驱动编写

    摘要:媒介 Device Tree是一种用去描绘硬件的数据布局,类似板级描绘说话,发源于OpenFirmware(OF).正在现在遍及应用的kernel 2.6.x版本中,对分歧仄台.分歧硬件,往] ...

  7. 【正点原子Linux连载】第六十七章 Linux USB驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 【正点原子MP157连载】第二十三章 Linux设备树-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  9. 【正点原子MP157连载】第四十章 Linux I2C驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

最新文章

  1. UI设计培训分享:ui设计师如何培养设计思维?
  2. 3行代码写出8个接口,开挂了?
  3. 2021年春季学期-信号与系统-第十三次作业参考答案-第一小题
  4. 泛域名Wildcard Domain
  5. java反射机制关键字驱动_搭建关键字驱动自动化测试框架
  6. Android开发之单例模式
  7. [置顶] SQL注入安全分析
  8. crontab文件在哪个目录_目录形式URL与文件形式URL哪个更有利于SEO
  9. py2exe打包python_Python打包-py2exe使用
  10. oracle shared_pool_size 0,Oracle 参数shared_pool_size
  11. OBS无延迟视频直播完整教程(组图)
  12. Gitlab和gitlab-runner安装和注册
  13. python ocr文字识别竖排繁体_(以繁体竖排为例)OCR各种软件使用效果对照..docx...
  14. SQL AlawaysOn 之四:故障转移集群
  15. 总结几点 Wake On Lan (WOL) 失败的原因
  16. 年末巨献|大数据盛会!企业大数据落地高峰论坛倒计时,速速报名!
  17. 计算机组成原理实验:存储器扩展电路(使用译码器)
  18. 使用Filemail免费发送大文件
  19. 让一维数组像糖果盒一样简单
  20. 【HDOJ】1003 Max Sum_天涯浪子_新浪博客

热门文章

  1. linux启动 profile,Linux 启动时profile、bashrc、~/.bash_profile、~/.bashrc、~/.bash_profile执行顺序以及文件说明...
  2. iphone5s 耳机更换插头 EarPods change jack
  3. android studio打包三星闪退,Android studio中apk打包好后,传到手机,安装好后闪退是什么原因,提示,,已停止运行...
  4. sdc——input delay,output delay
  5. 谷歌浏览器network请求时间(stalled,DNS Lookup,Waiting)分析以及解决方案
  6. called detach on an already detached child ViewHolder
  7. 算法竞赛入门【码蹄集进阶塔335题】(MT2330-2335)
  8. 虎牢关模式的心得与分析-秘籍心得
  9. 大数据分析常用组件、框架、架构介绍(Hadoop、Spark、Storm、Flume、Kafka、Logstash、HDFS、HBase)
  10. linux 创建文件命令总结