SPI Flash Rom W25Q16----基于STC15

xiaoxiao2021-02-28  30

百度文库一个很详细的介绍、、、、https://wenku.baidu.com/view/7db1401e1a37f111f0855b81.html?from=search (。。。。)

先来简单认识一下这个芯片 W25Q16

其实就是以SPI作为通信时序要求的一款储存芯片,升级版的EEPROM,比EEPROM读取速度快,价格还差不多。

W25X16分为8192页,每页256字节,用“页编程指令”每次就可以编程256字节,用“扇区擦除指令”每次可擦除16页,用“块擦除指令”每次可擦除256页,用“整片擦除指令”可一次擦除整个芯片,W25X16有512个可擦除扇区或32个可擦除块。

对于W25X16,1页=256字节,归纳一下,

1页=256字节 1扇区=16页=16*256字节=4096字节 (W25X16有512个扇区)。 1块=256页=256*256字节=65536字节 (W25X16有32块)。

W25X16引脚排列如下图

注意有一点比较坑的,,,, 关于IO口控制这个存储芯片。。。。。。。。

RP2的作用的限流,而RP5的作用就是上拉!还是外部上拉!!! 所以单片机要想IO控制这个存储芯片的电平,必须是开漏模式! 所以在下面的程序 main.c中可以看到这么几行

P1M0 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P1M1 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P0M0 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P0M1 = 0xFF;

就是讲P1口以及P0设置成开漏模式!

引脚重点说明:

DIO脚 :在普通方式下,这个引脚是串行输入引脚(DI),数据、地址和命令通过此引脚送到芯片内部,在CLK引脚的上升沿捕获。

当使用了“快读双输出指令”时,这个引脚就变成了DO引脚,这种情况下,芯片就有了2个DO引脚,所以叫做双输出,这时芯片的通信速度相当于翻了一倍,所以传输速度更快。

/HOLD脚: 保持引脚,当/CS片选为低电平,且HOLD为低电平时,DO引脚处于高阻态,而且会忽略DIO和CLK引脚上的信号,把HOLD拉高,器件恢复正常工作,当芯片与多个其它芯片共享单片机上的同一个SPI接口时,此引脚就显得非常有用,通常此引脚接高电平保证芯片正常工作。

W25Q16内部状态寄存器

(上电复位时,各位都被清零)

BUSY忙位:

只读位,在芯片执行“页编程”,“扇区擦除” 、“块擦除”、 “芯片擦除”、 “写状态寄存器”指令时,该位自动置1,此时除了“读状态寄存器”指令,其它指令都无效,当编程、擦除和写状态寄存器指令执行完毕后,该为自动变0,表示芯片可以接收其它指令了。

WEL写保护位:

只读位,写操作允许标志位,当执行完写使能指令后,该位为0表示允许写操作,为1表示禁止写,当芯片掉电后或执行写禁止、页编程、扇区擦除、块擦除、芯片擦除和写状态寄存器命令后自动进入写保护状态,即置1。

BP2、BP1、BP0块保护位:

可读写位,用于块区保护,可用写状态寄存器命令修改这几位,为这3位为0时,块区无保护,当SPR位为1或/WP脚为低时,这3位无法更改。

TB 底部顶部块保护位:

可读写位,用于底部顶部块保护,可用写状态寄存器命令修改这1位,当这1位为0时,底部顶部块区无保护,当SPR位为1或/WP脚为低时,这1位无法更改。

SPR状态寄存器保护位: 可读写位,意义如下表

重点来了::::

W25X16包括15个基本指令,通过这15个基本指令与SPI总线就完全可以控制芯片,指令在/CS拉低后开始传送,DIO引脚上数据的第一个字节就是指令码,在CLK引脚的上升沿采集DIO数据,高位在前。 指令的长度从1个字节到多个字节,有时还会跟随地址字节、数据字节、伪字节,有时候还会是它们的组合,在/CS引脚的上升沿完成指令的传输,所有的读指令都可以在任意时钟位完成,而所有的写、编程和擦除指令在一个字节的边界后才能完成,否则,指令将不起作用,这个特征可以保护芯片不被意外写入,当芯片正在被编程、擦除或写状态寄存器的时候,除“读状态寄存器”指令,其它所有指令都将被忽略直到擦写周期结束。

说明:数据高位在前,带括号的数据表示数据从DO引脚读出。

指令说明学习:

写使能06H:

写使能指令将会使状态寄存器WEL位置位,在执行每个“页编程”、“扇区擦除”、“块擦除”、“芯片擦除”和“写状态寄存器”命令之前,都要先置位WEL,*再把/CS脚先拉低之后*,“写使能”指令码06H从DIO引脚输入,在CLK上升沿采集,然后再拉高/CS引脚

写禁止04H:

时序与写使能相同,执行完“页编程”、“扇区擦除”、“块擦除”、“芯片擦除”和 “写状态寄存器”命令之后WEL位会自动变0,即自动进入写禁止状态。

读状态寄存器05H

当/CS拉低之后,开始把05H从DIO引脚送入芯片,在CLK的上升沿数据被芯片采集,当芯片认出采集到的数据是05H时,芯片就会把“状态寄存器”的值从DO引脚输出,数据在CLK的下降沿输出,高位在前。

读状态寄存器指令在任何时候都可以用,甚至在编程、擦除、写状态寄存器的过程中也可以用,这样就可从状态寄存器的BUSY位判断编程、擦除、写状态寄存器周期是否结束,从而让我们知道芯片是否可以接收下一指令,如果/CS不被拉高,状态寄存器的值将一直从DO脚输出,当/CS拉高后,该指令结束

写状态寄存器01H

在执行写状态寄存器指令以前,需要先按“写使能时序”执行完“写使能”指令,然后再次将/CS拉低,把01H从DIO引脚送入芯片,然后再把需要的状态寄存器的值送入芯片,拉高/CS,指令结束,如果此时没把/CS脚拉高,或者是拉得晚了,值将不会被写入,指令无效。

读数据03H

读数据指令允许读取一个或多个字节,先将/CS拉低,把03H从DIO引脚送入芯片,然后再把24位地址送入芯片,这些数据在时钟的上升沿被芯片采集,芯片收到24位在CLK引脚的下降沿从DO引脚输出,高位在前。当读完这个地址的数据后,地址自动增加,然后通过DO引脚把下一个地址的数据输出,也就是说,只要CLK在工作,通过一条指令就可把整个芯片储存区的数据全部读出来,把/CS脚拉高,“读数据”指令结束,当芯片在执行编程、擦除和读状态寄存器指令的周期内,“读数据”指令无效。

页编程02H

在执行页编程指令以前,需要先擦除整个待写入区域,保证待写入区域全为1,然后按“写使能时序”执行完“写使能”指令,再把/CS拉低,将02H从DIO引脚送入芯片,然后再把24位地址送入芯片,然后接着送要写入的字节到芯片,在写完数据后,把/CS拉高。写完一页后必须把地址改为0,不然的话,如果时钟还在继续,地址将自动变为页的开始地址,如果写入的字节不足256个字节的话,其它写入的字节都是无意义的,如果写入的字节大于256字节,多余的字节加上无用的字节覆盖刚刚写入的256字节,所以需要保证写入的字节小于或等于256字节。如果写入的地址处于写保护状态,“页编程”指令无效。

扇区擦除20H

扇区擦除指令将一个扇区(4096字节)擦除,擦除后扇区字节都为FFH,在执行扇区擦除指令以前,需要先按“写使能时序”执行完“写使能”指令,然后再次将/CS拉低,把20H从DIO引脚送入芯片,然后再把24位扇区地址送入芯片,然后拉高/CS,指令结束,如果此时没及时把/CS脚拉高,指令将不起作用。如果擦除的地址处于写保护状态,“扇区擦除”指令无效。

块擦除D8H

块擦除指令将一个块(65536字节)擦除,擦除后扇区字节都为FFH,在执行块擦除指令以前,需要先按“写使能时序”执行完“写使能”指令,然后再次将/CS拉低,把D8H从DIO引脚送入芯片,然后再把24位扇区地址送入芯片,然后拉高/CS,指令结束,如果此时没及时把/CS脚拉高,指令将不起作用。如果擦除的地址处于写保护状态,“块擦除”指令无效。

芯片擦除C7H

芯片擦除指令将整个芯片储存区擦除,擦除后整个芯片储存区字节都为FFH,在执行芯片擦除指令以前,需要先按“写使能时序”执行完“写使能”指令,然后再次将/CS拉低,把C7H从DIO引脚送入芯片,然后拉高/CS,指令结束,如果此时没及时把/CS脚拉高,指令将不起作用。任何一个块处于写保护状态,“块擦除”指令无效。

软件模拟SPI

emmmmm,,,,,,,来一个实例,实战一下效果更佳。。。 下面实验 晶振12M 波特率115200

嗯,先来说一下程序实现的功能。。。

程序如果正常执行,上电后先打印出一个’OK’ 0x01 —> 读器件ID 0x02 —> 读制造+器件ID 0x03 —> 读JEDEC ID 0x04 —> 读指定地址的1个字节数据 0x05 —> 向指定地址写入1个字节的数据 0x06 —> 读寄存器状态 0x07 —> 写使能 0x08 —> 向指定地址写入指定个数的字节数据 0x09 —> 擦除整个chip(芯片)的数据 0x0a —> 擦除指定扇区的数据 0x0b —> 测试数据页面 0x0c —> 读测试数据页面

#include <W25.H> #include "stc15f2k60s2.h" #include <intrins.h> extern uint8 upper_128[16]; extern uint8 tx_buff[16]; void delay_nms(uchar i) { uchar j; i=i*2; for(;i>0;i--) { j = 246; while(--j); } } void delay(uchar tt) { while(tt--);} //================================================================================================= //SPI_Read_StatusReg Reads the status register of the serial flash //SPI_Write_StatusReg Performs a write to the status register //SPI_Write_Enable Write enables the serial flash //SPI_Write_Disable Write disables the serial flash //SPI_Read_ID1 Reads the device ID using the instruction 0xAB //SPI_Read_ID2 Reads the manufacturer ID and device ID with 0x90 //SPI_Read_ID3() Reads the JedecDevice ID //SPI_Read_Byte Reads one byte from the serial flash and returns byte(max of 20 MHz CLK frequency) //SPI_Read_nBytes Reads multiple bytes(max of 20 MHz CLK frequency) //SPI_FastRead_Byte Reads one byte from the serial flash and returns byte(max of 33 MHz CLK frequency) //SPI_FastRead_nBytes Reads multiple bytes(max of 33 MHz CLK frequency) //SPI_Write_Byte Program one byte to the serial flash //SPI_Write_nBytes Program n bytes to the serial flash, n<=256 //SPI_Erase_Chip Erases entire serial flash //SPI_Erase_Sector Erases one sector (64 KB) of the serial flash //SPI_Wait_Busy Polls status register until busy bit is low //================================================================================================= uchar SPI_Read_StatusReg() //读状态寄存器 备注:Xcc { uchar byte = 0; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_ReadStatusReg); // send Read Status Register command byte = SPI_Get_Byte(); // receive byte W25X_CS = 1; // disable device return byte; } void SPI_Write_StatusReg(byte) //写状态寄存器 { W25X_CS = 0; // enable device SPI_Send_Byte(W25X_WriteStatusReg); // select write to status register SPI_Send_Byte(byte); // data that will change the status(only bits 2,3,7 can be written) W25X_CS = 1; // disable the device } void SPI_Write_Enable() //写使能 { W25X_CS = 0; // enable device SPI_Send_Byte(W25X_WriteEnable); // send W25X_Write_Enable command W25X_CS = 1; // disable device } void SPI_Write_Disable() //写禁能 { W25X_CS = 0; // enable device SPI_Send_Byte(W25X_WriteDisable); // send W25X_WriteW25X_DIsable command W25X_CS = 1; // disable device } uchar SPI_Read_ID1() //释放掉电/器件ID { uchar byte; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_DeviceID); // send read device ID command (ABh) SPI_Send_Byte(0); // send address SPI_Send_Byte(0); // send address SPI_Send_Byte(0); // send 3_Dummy address byte = SPI_Get_Byte(); // receive Device ID byte W25X_CS = 1; // disable device delay(4); // remain CS high for tRES2 = 1.8uS return byte; } uint SPI_Read_ID2(uchar ID_Addr) //制造/器件ID { uint IData16; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_ManufactDeviceID); // send read ID command (90h) SPI_Send_Byte(0x00); // send address SPI_Send_Byte(0x00); // send address SPI_Send_Byte(ID_Addr); // send W25Pxx selectable ID address 00H or 01H IData16 = SPI_Get_Byte()<<8; // receive Manufature or Device ID byte IData16 |= SPI_Get_Byte(); // receive Device or Manufacture ID byte W25X_CS = 1; // disable device return IData16; } uint SPI_Read_ID3() //读JEDEC ID { uint IData16; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_JedecDeviceID); // send read ID command (9Fh) IData16 = SPI_Get_Byte()<<8; // receive Manufature or Device ID byte IData16 |= SPI_Get_Byte(); // receive Device or Manufacture ID byte tx_buff[2] = SPI_Get_Byte(); W25X_CS = 1; // disable device return IData16; } uchar SPI_Read_Byte(uint32 Dst_Addr) //读某地址 数据 { uchar byte = 0; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_ReadData); // read command SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFFFF) >> 16));// send 3 address bytes SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte((uchar)(Dst_Addr & 0xFF)); byte = SPI_Get_Byte(); W25X_CS = 1; // disable device return byte; // return one byte read } void SPI_Read_nBytes(uint32 Dst_Addr, uchar nBytes_128) //读某地址起nBytes_128字节以内内容 { uint32 i = 0; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_ReadData); // read command SPI_Send_Byte(((Dst_Addr & 0xFFFFFF) >> 16)); // send 3 address bytes SPI_Send_Byte(((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte(Dst_Addr & 0xFF); for (i = 0; i < nBytes_128; i++) // read until no_bytes is reached upper_128[i] = SPI_Get_Byte(); // receive byte and store at address 80H - FFH W25X_CS = 1; // disable device } uchar SPI_FastRead_Byte(uint32 Dst_Addr) //快读 某地址 数据 { uchar byte = 0; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_FastReadData); // fast read command SPI_Send_Byte(((Dst_Addr & 0xFFFFFF) >> 16)); // send 3 address bytes SPI_Send_Byte(((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte(Dst_Addr & 0xFF); SPI_Send_Byte(0xFF); // dummy byte byte = SPI_Get_Byte(); W25X_CS = 1; // disable device return byte; // return one byte read } void SPI_FastRead_nBytes(uint32 Dst_Addr, uchar nBytes_128) //快读 某地址 nBytes_128 个字节数据 { uchar i = 0; W25X_CS = 0; // enable device SPI_Send_Byte(W25X_FastReadData); // read command SPI_Send_Byte(((Dst_Addr & 0xFFFFFF) >> 16)); // send 3 address bytes SPI_Send_Byte(((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte(Dst_Addr & 0xFF); SPI_Send_Byte(0xFF); // dummy byte for (i = 0; i < nBytes_128; i++) // read until no_bytes is reached upper_128[i] = SPI_Get_Byte(); // receive byte and store at address 80H - FFH W25X_CS = 1; // disable device } void SPI_Write_Byte(uint32 Dst_Addr, uchar byte) //但字节写入 { W25X_CS = 0; // enable device SPI_Write_Enable(); // set WEL SPI_Wait_Busy(); W25X_CS = 0; SPI_Send_Byte(W25X_PageProgram); // send Byte Program command SPI_Send_Byte(((Dst_Addr & 0xFFFFFF) >> 16)); // send 3 address bytes SPI_Send_Byte(((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte(Dst_Addr & 0xFF); SPI_Send_Byte(byte); // send byte to be programmed W25X_CS = 1; // disable device } void SPI_Write_nBytes(uint32 Dst_Addr, uchar nBytes_128) //页编程 128个字节 { uchar i, byte; W25X_CS = 0; /* enable device */ SPI_Write_Enable(); /* set WEL */ W25X_CS = 0; SPI_Send_Byte(W25X_PageProgram); /* send Byte Program command */ SPI_Send_Byte(((Dst_Addr & 0xFFFFFF) >> 16)); /* send 3 address bytes */ SPI_Send_Byte(((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte(Dst_Addr & 0xFF); for (i = 0; i < nBytes_128; i++) { byte = upper_128[i]; SPI_Send_Byte(byte); /* send byte to be programmed */ } W25X_CS = 1; /* disable device */ } void SPI_Erase_Chip() //擦除芯片 { W25X_CS = 0; // enable device SPI_Write_Enable(); // set WEL W25X_CS = 0; SPI_Wait_Busy(); W25X_CS = 0; SPI_Send_Byte(W25X_ChipErase); // send Chip Erase command W25X_CS = 1; // disable device } void SPI_Erase_Sector(uint32 Dst_Addr) //扇区擦除 { W25X_CS = 0; // enable device SPI_Write_Enable(); // set WEL W25X_CS = 0; SPI_Send_Byte(W25X_SectorErase); // send Sector Erase command SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFFFF) >> 16));// send 3 address bytes SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte((uchar)Dst_Addr & 0xFF); W25X_CS = 1; // disable device } void SPI_Wait_Busy() //等待忙结束 { while (SPI_Read_StatusReg() == 0x03) SPI_Read_StatusReg(); // waste time until not busy WEL & Busy bit all be 1 (0x03) } void SPI_PowerDown() { W25X_CS = 0; // enable device SPI_Send_Byte(W25X_PowerDown); // send W25X_PowerDown command 0xB9 W25X_CS = 1; // disable device delay(6); // remain CS high for tPD = 3uS } void SPI_ReleasePowerDown() { W25X_CS = 0; // enable device SPI_Send_Byte(W25X_ReleasePowerDown); // send W25X_PowerDown command 0xAB W25X_CS = 1; // disable device delay(6); // remain CS high for tRES1 = 3uS } #ifdef SST_SPI void SPI_init() { P1 = 0xFF; SPCR = 0x50; } void SPI_Send_Byte(uchar out) { unsigned char temp; SPDR = out; do { temp = SPSR & 0x80; } while (temp != 0x80); SPSR = SPSR & 0x7F; } uchar SPI_Get_Byte() { unsigned char temp; SPDR = 0x00; do { temp = SPSR & 0x80; } while (temp != 0x80); SPSR = SPSR & 0x7F; return SPDR; } #endif #ifndef SST_SPI void SPI_init() { W25X_CLK = 0; // set clock to low initial state for SPI operation mode 0 // W25X_CLK = 1; // set clock to High initial state for SPI operation mode 3 // _hold = 1; // W25X_WP = 1; W25X_CS = 1; SPI_Write_Disable(); } void SPI_Send_Byte(uchar out) { uchar i = 0; for (i = 0; i < 8; i++) { if ((out & 0x80) == 0x80) // check if MSB is high W25X_DI = 1; else W25X_DI = 0; // if not, set to low W25X_CLK = 1; // toggle clock high out = (out << 1); // shift 1 place for next bit nop();nop();nop();nop(); W25X_CLK = 0; // toggle clock low } } uchar SPI_Get_Byte() { uchar i = 0, in = 0, temp = 0; for (i = 0; i < 8; i++) { in = (in << 1); // shift 1 place to the left or shift in 0 temp = W25X_DO; // save input W25X_CLK = 1; // toggle clock high if (temp == 1) // check to see if bit is high in |= 0x01; // if high, make bit high W25X_CLK = 0; // toggle clock low } return in; } #endif

#include "stc15f2k60s2.h" // 单片机STC15F2K60S2头文件,可以不再加入reg51.h #include <intrins.h> // 加入此头文件后,可使用_nop_库函数 #include "delay.h" // 延时函数头文件 #include "W25.H" #define uint8 unsigned char #define uint16 unsigned int #define uchar unsigned char #define uint unsigned int #define uint32 unsigned long void init_cpu(void); void delay(uchar tt); void trace(uchar *str,uchar len); void test_page(uchar addr); void read_page(uchar addr); void Verify(uchar byte, uchar cor_byte); uint8 Rxtemp; bit MYTI; uint8 tx_buff[16]; uint8 upper_128[16]; bit rx_ok; void main(void) { uint i; P1M0 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P1M1 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P0M0 = 0xFF; //将SPI CS引脚P3.5设为开漏输出, 使用外置3.3V上拉. P0M1 = 0xFF; // W25X_HOLD= 1; init_cpu(); SPI_init(); tx_buff[0]='O'; tx_buff[1]='K'; trace(tx_buff,2); for(;;) { if(rx_ok == 1) { rx_ok = 0; switch(Rxtemp) { case 0x01: Rxtemp = 0; tx_buff[0] = SPI_Read_ID1(); trace(tx_buff,1); break; case 0x02: i = SPI_Read_ID2(0x00); tx_buff[1] = (uchar)i; tx_buff[0] = (uchar)(i>>8); trace(tx_buff,2); break; case 0x03: i = SPI_Read_ID3(); tx_buff[1] = (uchar)i; tx_buff[0] = (uchar)(i>>8); trace(tx_buff,3); break; case 0x04: tx_buff[0] = SPI_Read_Byte(0x00000000); trace(tx_buff,1); break; case 0x05: tx_buff[0] = 0x55; SPI_Write_Byte(0x00000000,0xa5); trace(tx_buff,1); break; case 0x06: tx_buff[0] = SPI_Read_StatusReg(); trace(tx_buff,1); break; case 0x07: SPI_Write_Enable(); break; case 0x08: upper_128[0]=0x01;upper_128[1]=0x02;upper_128[2]=0x03;upper_128[3]=0x04; SPI_Write_nBytes(0x00000000,4); break; case 0x09: SPI_Erase_Chip(); break; case 0x0a: SPI_Erase_Sector(0x000ff000); while(1) { tx_buff[0] = SPI_Read_StatusReg(); if(tx_buff[0] == 0) { trace(tx_buff,1); break; } } break; case 0x0b: test_page(0x00); break; case 0x0c: read_page(0x00); break; default: break; } } } } void init_cpu(void) { // SCON = 0x50; //8位数据,可变波特率 18.432M // AUXR |= 0x40; //定时器1时钟为Fosc,即1T // AUXR &= 0xFE; //串口1选择定时器1为波特率发生器 // TMOD &= 0x0F; //设定定时器1为16位自动重装方式 // TL1 = 0x20; //设定定时初值 // TH1 = 0xFE; //设定定时初值 // ET1 = 0; //禁止定时器1中断 // TR1 = 1; //启动定时器1 SCON = 0x50; //8位数据,可变波特率 12M AUXR |= 0x40; //定时器1时钟为Fosc,即1T AUXR &= 0xFE; //串口1选择定时器1为波特率发生器 TMOD &= 0x0F; //设定定时器1为16位自动重装方式 TL1 = 0xC7; //设定定时初值 TH1 = 0xFE; //设定定时初值 ET1 = 0; //禁止定时器1中断 TR1 = 1; //启动定时器1 ES = 1; //uart interrupt enable EA = 1; //all interrupt enable } //串口中断程序 void UART_isr(void) interrupt 4 { if(RI) { RI = 0; Rxtemp = SBUF; //接收 //SBUF = Rxtemp; //发送 rx_ok = 1; return; } if(TI) { TI = 0; MYTI = 1; } } void test_page(uchar addr) { uint i; uchar byte; uint32 Dst_Addr; W25X_CS = 0; // enable device SPI_Write_Enable(); // set WEL W25X_CS = 0; Dst_Addr = (uint32)addr*256; Dst_Addr = 0x0ff000;//(uint32)addr*256; SPI_Send_Byte(W25X_PageProgram); // send Byte Program command SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFFFF) >> 16)); // send 3 address bytes SPI_Send_Byte((uchar)((Dst_Addr & 0xFFFF) >> 8)); SPI_Send_Byte((uchar)(Dst_Addr & 0xFF)); for (i = 0; i < 256; i++) // send byte to be programmed SPI_Send_Byte(i); W25X_CS = 1; delay_nms(5); W25X_CS = 0; while(1) { tx_buff[0] = SPI_Read_StatusReg(); trace(tx_buff,1); if(tx_buff[0] == 0) break; } Dst_Addr = 0x0ff000; for (i = 0; i < 256; i++) { byte = SPI_Read_Byte(Dst_Addr+i); ES = 0; SBUF = byte; while (TI == 0); TI = 0; ES = 1; } W25X_CS = 1; } //================================================================================================= void read_page(uchar addr) { uint i; uchar byte; uint32 Dst_Addr; Dst_Addr = addr*256; Dst_Addr = 0x0ff000; W25X_CS = 0; for (i = 0; i < 256; i++) { byte = SPI_Read_Byte(Dst_Addr+i); ES = 0; SBUF = byte; while (TI == 0); TI = 0; ES = 1; } W25X_CS = 1; } //================================================================================================= void Verify(uchar byte, uchar cor_byte) { if (byte != cor_byte) { while(1); //LED_Error = 0; /* display to view error on LED. */ } } //================================================================================================= void myputchar(uchar c) { ES = 0; SBUF = c; while (TI == 0); TI = 0; ES = 1; } //================================================================================================= void trace(uchar *str,uchar len) { uint i; for(i=0;i<len;i++) { myputchar(*str); str++; } }

#ifndef _W25_H_ #define _W25_H_ #include "stc15f2k60s2.h" #define uint8 unsigned char #define uint16 unsigned int #define uchar unsigned char #define uint unsigned int #define uint32 unsigned long //sbit W25X_HOLD = P1^2; //sbit W25X_WP = P1^1; sbit W25X_CS = P0^5; sbit W25X_DI = P1^3; sbit W25X_DO = P1^4; sbit W25X_CLK = P1^5; #define nop() _nop_() #define W25X_WriteEnable 0x06 //写使能 备注:xcc #define W25X_WriteDisable 0x04 //写禁能 #define W25X_ReadStatusReg 0x05 //读状态寄存器 #define W25X_WriteStatusReg 0x01 //写状态寄存器 #define W25X_ReadData 0x03 //读数据 #define W25X_FastReadData 0x0B //快读 #define W25X_FastReadDual 0x3B //快读双输出 #define W25X_PageProgram 0x02 //页编程 #define W25X_BlockErase 0xD8 //块擦除 #define W25X_SectorErase 0x20 //扇区擦除 #define W25X_ChipErase 0xC7 //芯片擦除 #define W25X_PowerDown 0xB9 //掉电 #define W25X_ReleasePowerDown 0xAB //释放掉电 #define W25X_DeviceID 0xAB //器件ID #define W25X_ManufactDeviceID 0x90 //制造/器件ID #define W25X_JedecDeviceID 0x9F //JEDEC ID uchar SPI_Read_StatusReg(); void SPI_Write_StatusReg(byte); void SPI_Write_Enable(); void SPI_Write_Disable(); uchar SPI_Read_ID1(); uint SPI_Read_ID2(uchar ID_Addr); uint SPI_Read_ID3(); uchar SPI_Read_Byte(uint32 Dst_Addr); void SPI_Read_nBytes(uint32 Dst_Addr, uchar nBytes_128); uchar SPI_FastRead_Byte(uint32 Dst_Addr); void SPI_FastRead_nBytes(uint32 Dst_Addr, uchar nBytes_128); void SPI_Write_Byte(uint32 Dst_Addr, uchar byte); void SPI_Write_nBytes(uint32 Dst_Addr, uchar nBytes_128); void SPI_Erase_Chip(); void SPI_Erase_Sector(uint32 Dst_Addr); void SPI_Wait_Busy(); void SPI_PowerDown(); void SPI_ReleasePowerDown(); void SPI_init(); void SPI_Send_Byte(uchar out); uchar SPI_Get_Byte(); void delay_nms(uchar i); void delay(uchar tt); #endif

上述结果就不在一一上图试验了,,,躬行且体会即可。

硬件SPI实现 ##

基于官方例程修改 下面实验 晶振11.0592M 波特率9600

手记:SPIF和WCOL较为特殊必须写1才能清0

关于程序实现的功能:

从0x00000000开始的256个字节赋值为 0~0xFF,写入到flash,清空数据数组,在从0x00000000开始的256个字节读出数据存入数据数组,打印到串口。

#include "reg51.h" typedef bit BOOL; typedef unsigned char BYTE; typedef unsigned short WORD; typedef unsigned long DWORD; #define FOSC 11059200L #define BAUD (65536 - FOSC / 4 / 115200) #define NULL 0 #define FALSE 0 #define TRUE 1 sfr P0M1 = 0x93; sfr P0M0 = 0x94; sfr P1M1 = 0x91; sfr P1M0 = 0x92; sfr P2M1 = 0x95; sfr P2M0 = 0x96; sfr P3M1 = 0xb1; sfr P3M0 = 0xb2; sfr P4M1 = 0xb3; sfr P4M0 = 0xb4; sfr P5M1 = 0xC9; sfr P5M0 = 0xCA; sfr P6M1 = 0xCB; sfr P6M0 = 0xCC; sfr P7M1 = 0xE1; sfr P7M0 = 0xE2; sfr AUXR = 0x8e; //辅助寄存器 sfr P_SW1 = 0xa2; //外设功能切换寄存器1 #define SPI_S0 0x04 #define SPI_S1 0x08 sfr SPSTAT = 0xcd; //SPI状态寄存器 #define SPIF 0x80 //SPSTAT.7 #define WCOL 0x40 //SPSTAT.6 sfr SPCTL = 0xce; //SPI控制寄存器 #define SSIG 0x80 //SPCTL.7 #define SPEN 0x40 //SPCTL.6 #define DORD 0x20 //SPCTL.5 #define MSTR 0x10 //SPCTL.4 #define CPOL 0x08 //SPCTL.3 #define CPHA 0x04 //SPCTL.2 #define SPDHH 0x00 //CPU_CLK/4 #define SPDH 0x01 //CPU_CLK/16 #define SPDL 0x02 //CPU_CLK/64 #define SPDLL 0x03 //CPU_CLK/128 sfr SPDAT = 0xcf; //SPI数据寄存器 sbit SS = P0^5; //SPI的SS脚,连接到Flash的CE #define SFC_WREN 0x06 //串行Flash命令集 #define SFC_WRDI 0x04 #define SFC_RDSR 0x05 #define SFC_WRSR 0x01 #define SFC_READ 0x03 #define SFC_FASTREAD 0x0B #define SFC_RDID 0xAB #define SFC_PAGEPROG 0x02 #define SFC_RDCR 0xA1 #define SFC_WRCR 0xF1 #define SFC_SECTORER 0xD7 #define SFC_BLOCKER 0xD8 #define SFC_CHIPER 0xC7 void InitUart(); void SendUart(BYTE dat); void InitSpi(); BYTE SpiShift(BYTE dat); BOOL FlashCheckID(); BOOL IsFlashBusy(); void FlashWriteEnable(); void FlashErase(); void FlashRead(DWORD addr, DWORD size, BYTE *buffer); void FlashWrite(DWORD addr, DWORD size, BYTE *buffer); #define BUFFER_SIZE 1024 //缓冲区大小 #define TEST_ADDR 0 //Flash测试地址 BYTE xdata g_Buffer[BUFFER_SIZE]; //Flash读写缓冲区 BOOL g_fFlashOK; //Flash状态 void main() { int i; P0M0 = 0xFF; P0M1 = 0xFF; P1M0 = 0xFF; P1M1 = 0xFF; //初始化Flash状态 g_fFlashOK = FALSE; //初始化串口和SPI InitUart(); InitSpi(); //检测Flash状态 FlashCheckID(); //TEST FlashErase(); for(i=0; i<256; i++) g_Buffer[i] = i; FlashWrite(TEST_ADDR, 256, g_Buffer); for(i=0; i<256; i++) g_Buffer[i] = 0; FlashRead(TEST_ADDR, 256, g_Buffer); for(i=0; i<256; i++) SendUart(g_Buffer[i]); while (1); } /************************************************ 串口初始化 入口参数: 无 出口参数: 无 ************************************************/ void InitUart() { AUXR = 0x40; //设置定时器1为1T模式 TMOD = 0x00; //定时器1为16位重载模式 TH1 = BAUD >> 8; //设置波特率 TL1 = BAUD; TR1 = 1; SCON = 0x5a; //设置串口为8位数据位,波特率可变模式 } /************************************************ 发送数据到串口 入口参数: dat : 准备发送的数据 出口参数: 无 ************************************************/ void SendUart(BYTE dat) { while (!TI); //等待上一个数据发送完成 TI = 0; //清除发送完成标志 SBUF = dat; //触发本次的数据发送 } /************************************************ SPI初始化 入口参数: 无 出口参数: 无 ************************************************/ void InitSpi() { SPSTAT = SPIF | WCOL; //清除SPI状态 1100 0000 SPIF和WCOL较为特殊必须写1才能清0 SS = 1; SPCTL = SSIG | SPEN | MSTR; //设置SPI为主模式 // 1101 0000 } /************************************************ 使用SPI方式与Flash进行数据交换 入口参数: dat : 准备写入的数据 出口参数: 从Flash中读出的数据 ************************************************/ BYTE SpiShift(BYTE dat) { SPDAT = dat; //触发SPI发送 while (!(SPSTAT & SPIF)); //等待SPI数据传输完成 SPSTAT的第7位SPIF在不断更新,不断和SPIF是1相与,当发送完毕后两者相与为1,取非为0,退出while SPSTAT = SPIF | WCOL; //清除SPI状态 //写1清除 return SPDAT; } /************************************************ 检测Flash是否准备就绪 入口参数: 无 出口参数: 0 : 没有检测到正确的Flash 1 : Flash准备就绪 ************************************************/ BOOL FlashCheckID() { BYTE dat; SS = 0; SpiShift(SFC_RDID); //发送读取ID命令 SpiShift(0x00); //发送三个地址字节 SpiShift(0x00); SpiShift(0x00); dat = SpiShift(0x00); //读取制造商ID1(释放掉电/器件ID) SS = 1; //SendUart(dat); if(dat == 0x14) //检测是否为25QX系列系列的Flash g_fFlashOK = 1; return g_fFlashOK; } /************************************************ 检测Flash的忙状态 入口参数: 无 出口参数: 0 : Flash处于空闲状态 1 : Flash处于忙状态 ************************************************/ BOOL IsFlashBusy() { BYTE dat; SS = 0; SpiShift(SFC_RDSR); //发送读取状态命令 dat = SpiShift(0); //读取状态 SS = 1; return (dat & 0x01); //状态值的Bit0即为忙标志 } /************************************************ 使能Flash写命令 入口参数: 无 出口参数: 无 ************************************************/ void FlashWriteEnable() { while (IsFlashBusy()); //Flash忙检测 SS = 0; SpiShift(SFC_WREN); //发送写使能命令 SS = 1; } /************************************************ 擦除整片Flash 入口参数: 无 出口参数: 无 ************************************************/ void FlashErase() { if (g_fFlashOK) { FlashWriteEnable(); //使能Flash写命令 SS = 0; SpiShift(SFC_CHIPER); //发送片擦除命令 SS = 1; } } /************************************************ 从Flash中读取数据 入口参数: addr : 地址参数 size : 数据块大小 buffer : 缓冲从Flash中读取的数据 出口参数: ************************************************/ void FlashRead(DWORD addr, DWORD size, BYTE *buffer) { if (g_fFlashOK) { while (IsFlashBusy()); //Flash忙检测 SS = 0; SpiShift(SFC_FASTREAD); //使用快速读取命令 SpiShift(((BYTE *)&addr)[1]); //设置起始地址 SpiShift(((BYTE *)&addr)[2]); SpiShift(((BYTE *)&addr)[3]); SpiShift(0); //需要空读一个字节 while (size) { *buffer = SpiShift(0); //自动连续读取并保存 addr++; buffer++; size--; } SS = 1; } } /************************************************ 写数据到Flash中 入口参数: addr : 地址参数 size : 数据块大小 buffer : 缓冲需要写入Flash的数据 出口参数: 无 ************************************************/ void FlashWrite(DWORD addr, DWORD size, BYTE *buffer) { if (g_fFlashOK) while (size) { FlashWriteEnable(); //使能Flash写命令 SS = 0; SpiShift(SFC_PAGEPROG); //发送页编程命令 SpiShift(((BYTE *)&addr)[1]); //设置起始地址 SpiShift(((BYTE *)&addr)[2]); SpiShift(((BYTE *)&addr)[3]); while (size) { SpiShift(*buffer); //连续页内写 addr++; buffer++; size--; if ((addr & 0xff) == 0) break; } SS = 1; } }

这个必须上一下实验的结果图了。。。。。

ReCclay 认证博客专家 嵌入式软件开发 机器/深度学习 全栈技术学习者 大家好,我是博主ReCclay,目前处于研究生阶段,就读于电子科技大学,主攻方向为汽车辅助驾驶算法研究。入站以来,凭借坚持与热爱,以博文的方式分享所学,截止目前累计博文数量达800余篇,累计受益人次达130w+次,涉及领域包括但不限于物联网开发、单片机开发、Linux驱动开发、FPGA开发、前/后端软件开发等。在未来我将继续专注于嵌入式相关领域,学习更多的科技知识,输出更高质量的博文。
转载请注明原文地址: https://www.6miu.com/read-1599999.html

最新回复(0)