STC89C52是经典的C51单片机,该芯片不自带硬件SPI接口,正好有手上一块W25Q32的存储模块(某宝上买的2.2元),试着使用89C52模拟SPI接口驱动W25Q32,在驱动的过程中遇到了几个问题,首先的问题是电平不匹配,其次是对芯片datasheet资料的解读,关于W25Qx的资料基本上是全英文的资料,笔者英文水平有限解读起来有一定的困难,只能一点点去解读;其次网络上关于使用C51驱动W25Qx的相关资料有限,很大部分都是使用stm32芯片驱动的案例,结合stm32案例实现W25Qx的驱动,以下内容为个人学习过程小结,由于笔者水平有限,难免有错误,敬请谅解。

一、电路搭建(解决芯片IO电平匹配):




W25Q256FVFIG驱动程序和实例代码 c5255驱动_单片机


依据W25Qx芯片资料,该芯片支持电平范围为2.7V-3.6V,过电平可能造成损坏芯片,而89C52的IO电平为5V。解决办法是加一块3.3V转5V的电平转换模块(TXS-0108E),电路的连接如下图:


W25Q256FVFIG驱动程序和实例代码 c5255驱动_数据_02


另外网络上也有采用电阻限流的方案,由于元件较多,焊接有点麻烦,笔者未验证,提供下图,仅供学习参考。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_03


二、编写软SPI驱动:

SPI接口一般使用 4 条线通信:

DO 主设备数据输入,从设备数据输出。

DI 主设备数据输出,从设备数据输入。

SCK 时钟信号,由主设备产生。

CS 从设备片选信号,由主设备控制。

SPI 主要特点有:可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。

以下是SPI传送时序,SPI传送时序有4种方式,方式0-方式3。

方式0(0,0),方式1(0,1),方式2(1,0),方式3(1,1)


W25Q256FVFIG驱动程序和实例代码 c5255驱动_单片机_04


SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;SPI主模块和与之通信的外设时钟相位和极性应该一致。CPOL是用来决定SCK时钟信号空闲时的电平,CPOL=0,空闲电平为低电平,CPOL=1时,空闲电平为高电平。CPHA是用来决定采样时刻的,CPHA=0,在每个周期的第一个时钟沿采样,CPHA=1,在每个周期的第二个时钟沿采样。

小结下上述内容:CPOL=0时起始电平为低电平,CPOL=1时起始电平为高电平。CPHA=0时,CLK的第一个上升沿为采样(写W25Qx), CLK的第一个下降沿为输出(读W25Qx),CPHA=1时,CLK的第一个下降沿为采样(写W25Qx), CLK的第一个上升沿为输出(读W25Qx)。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_05


W25Qx的SPI传输支持方式0和方式3,即方式0(CPOL=0,CPHA=0),方式3(CPOL=1,CPHA=1)。

为了方便起见,笔者以方式0为基础,编写一个SPI的传输函数,该函数发送以MSB(高位优先传输)。

//SPI传输函数,发送与接收一个字节
unsignedchar SPI_Byte(unsigned char dat)
{
 unsigned charread=0,i=0;
 for(i=0;i<8;i++)
 {
 W25_DI=dat&0x80;
 W25_CLK=1;
 dat<<=1;
 read<<=1;
 read|=W25_DO; 
 W25_CLK=0;
 }
 return read; 
}

三、编写函数测试获取芯片的Flash ID号(JEDEC ID):

当电路搭建完成后,SPI通信基础函数编写完成后,下一步需要获取芯片返回一个值,测试电路与基础函数成功与否。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_数据_06


依据资料文件,获取ID之前有两点需要注意,第一点是MSB高位优先传输,第二点是获取操作时CS必须拉低。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_单片机_07


当获取成功后W25Q32对应的16位的码值为0x4016,下面依据芯片资料提供的时序。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_08


依据上图小结如下信息。

1、获取时CS必须为低电平,高位优先传输。

2、获取JEDEC ID的指令为0x90。

3、接收到信息三个字节,第一个字节为ManufacturerID(制造商ID)固定值为0xEF,第二个字节为MemoryType(内存类型),第三个字节为Capacity(容量)。

编写代码如下:

unsignedlong W25x_read_ID()
{
 unsigned long re,t1,t2,t3;
 W25_CS=0;
 W25_CLK=0;
 SPI_Byte(0x9F); 
 t3=SPI_Byte(0xff);
 t2=SPI_Byte(0xff);
 t1=SPI_Byte(0xff);
 W25_CS=1; 
 re=(t3<<16)|(t2<<8)|t1;
 return re;
}

测试结果:


W25Q256FVFIG驱动程序和实例代码 c5255驱动_51单片机_09


四、芯片写使能与忙状态等待。

1、写使能:

写使能的操作与读ID的操作类似,其操作指令为0x06,写使能成功后Status Register1的S1位WSL置1,即允许写操作与擦除操作。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_10


//写使能
voidW25x_write_Enable()
{
 W25_CS=0;
 W25_CLK=0;
 SPI_Byte(0x06);
 W25_CS=1;
}

2、忙状态等待函数:

当芯片正在执行擦除、写操作时,不允许其它操作,此时必须进入等待状态,直到执行完成后Status Register1的S0位BUSY重新置0后才允许后续操作,函数必须不断的读取状态寄存器,从中提取S0的数据,从而确定相应操作的状态。Status Register1读取指令为0x05,读取方式为MSB那么S0位对应与操作位为0x01,操作方式与读ID的操作类似,依据下图资料的时序,编写函数如下。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_11


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_12


//等待擦除或写入操作,直到操作完成结束。
voidW25x_wait_BusyEnd()
{
 unsigned char i=0,temp;
 W25_CS=0;
 W25_CLK=0;
 SPI_Byte(0x05);
 temp=SPI_Byte(0xff);
 while((temp&0x01)==1)
 {
 temp=SPI_Byte(0xff);
 } 
 W25_CS=1;
}

五、编写擦除指令:

W25Q32为非易失性存储器,写数据之前必须先执行擦除操作,这里我们先编写一个芯片擦除函数。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_13


W25Q256FVFIG驱动程序和实例代码 c5255驱动_上升沿_14


小结上图芯片资料信息:

1、所谓的芯片擦除是往存储单元字节中填充0xFF即为擦除。

2、芯片擦除操作前必须开启Write Enable(写使能)之前有介绍。

3、芯片擦除操作的指令0x07或0x60,当然CS也必须低电平。

4、芯片擦除完成后需要等待一段时间(读忙状态),之前有介绍。

5、其它与读取ID操作类似。

//芯片擦除操作函数
voidW25x_chipErase()
{
 W25x_write_Enable();//开使能
 W25_CS=0;
 W25_CLK=0; 
 SPI_Byte(0xC7); 
 W25_CS=1;
 W25x_wait_BusyEnd();//等待擦除操作完成结束
}

六、页写入操作:

W25Qx最大写入单位为页(Page),每页最多写入256B,超过256B必须另写一页,每16页组成一个扇区(Sector),每16个扇区组成一个块(Block)。W25x32为4MB的容量,共有64个块,1024个扇区,16384个页。地址范围为(0x000000-0x2FFFFF),理论上地址每一位对应一个字节,实际在连续写入大数据的过程中还需考虑页、扇区、块的容量问题。注意写之前必须确保是擦除操作过的,最小的擦除单位不是页而是扇区。下面的例子仅考虑页写,理想的起始地址为页的起始位置,内容小于256B。

1块=16扇区,1扇区=16页,1页=256字节


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_15


W25Q256FVFIG驱动程序和实例代码 c5255驱动_数据_16


小结上图芯片资料如下:

1、页写操作与擦除操作类似,需要CS低电平、写使能、忙等待。

2、页写操作指令为0x02,接下来是24位地址(页的每页的起始地址为:0xXXXX00,页结束地址为0xXXXXFF其中X为16进制任意数)。

3、写入地址完成后,再次写入不大于256B的数据内容

页写代码如下:

//页写操作,*buf为内容,addr为地址,PageSize为写入字节数必//须小于256个
voidW25x_write_Page(unsigned char *buf,unsigned long addr,unsigned int Pagesize)
{
 W25x_write_Enable();
 W25_CS=0; 
 W25_CLK=0; 
 SPI_Byte(0x02);
 SPI_Byte(addr>>16&0xff);
 SPI_Byte(addr>>8&0xff);
 SPI_Byte(addr&0xff);
 if(Pagesize>256)
 {
 Pagesize=256;
 }
 while(Pagesize--)
 {
 SPI_Byte(*buf++);
 } 
 W25_CS=1;
 W25x_wait_BusyEnd();
}

七、读操作:

读操作相对于写操作比较简单,不用考虑页、扇区、块,仅使用24位地址即可读。


W25Q256FVFIG驱动程序和实例代码 c5255驱动_数据_17


小结上图芯片资料如下:

1、读操作与读ID操作类似,需要CS低电平。

2、读操作指令为0x03,接下来是24位地址。

3、写入地址完成后直接读出数据内容。

//读操作,*buf为内容,addr为地址,PageSize为读出的字节数
voidW25x_read_Page(unsigned char *buf,unsigned long addr,unsigned int Pagesize)
{ 
 W25_CS=0; 
 W25_CLK=0; 
 SPI_Byte(0x03);
 SPI_Byte(addr>>16&0xff);
 SPI_Byte(addr>>8&0xff);
 SPI_Byte(addr&0xff);
 while(Pagesize--)
 {
 *buf=SPI_Byte(0xff);
 buf++;
 } 
 W25_CS=1;
}

八、读写测试

数组a为写入内容,数据b为读出数据,i为芯片ID信息,所有信息通过串口发送至电脑。

voidmain()
{
 unsigned long i; 
 unsigned chara[]={0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x10,0x11,0x12,0x13};//写入内容
 unsigned char b[15];//读出内容
 char j;
 uart_init(); //串口初始化
 i=W25x_read_ID();//读ID
 uart_sendbyte(i/256/256);
 uart_sendbyte(i/256);
 uart_sendbyte(i%256);
 W25x_chipErase();//芯片擦除
 W25x_write_Page(a,0x000002,15);//从02位置开始写入
 W25x_read_Page(b,0x000000,15);//从00位置开始读信息 
 for(j=0;j<15;j++)
 {
 uart_sendbyte(b[j]);//输出所读内容
 }
 while(1);
}

输出结果:


W25Q256FVFIG驱动程序和实例代码 c5255驱动_Powered by 金山文档_18


实物展示:


W25Q256FVFIG驱动程序和实例代码 c5255驱动_数据_19