image.png

xx代表数值。

24位地址最大的地址就是2的24次方,也就是 16,777,216,它除以1024,结果为16,384,再除以1024就是16MB,也就是说24位地址最大只能表示到16MB,所以最后一个型号有两种地址存储模式,一种是3字节的,一种是四字节的,在16MB之前是3字节,16之后是4字节。

厂商不想太过浪费,就把MISO以及MOSI两根线都改成了可发可收的线,这种同时可以进行发送的线时钟频率就是16MHz,但是这个并不是时钟的频率,时钟频率最高依旧是80MHz,只是因为一个时钟边沿发送两位数据,所以这个信息传送的速度夸你了两倍。对于320MHz,就是在改芯片上有另外两根线在空闲的时候可以有数据通信的能力。

image.png

/以及上面画个横线代表低电平有效

 wp低电平有效,低电平的时候保护住,不让写,高电平的时候,可以写。

hold的功能就是如果你正在执行正常的读写时,进行中断,在spi突然被调用去控制器件其他进行通信的时候,如果把CS置回高电平,时序终止,如果不想终止总线,又想操作其他器件, hold置低电平,那么原本的时序不会消失,记住当前状态,同时SPI可以去执行其他的任务,执行完了之后就可以回到原本的继续执行原本的时序。

括号中的io几其实就是在进行多少重的时候看的,双重SPI就是io01,四重就是io0123。

wp以及hold如果要使用的话就直接放在STM32的GPIO口就行了。

w25q64框图

image.png

 它的总存储空间位8MB,但是均匀以每块64KB分成127份(1MB等于1024KB,因此8MB等于8 × 1024KB,即8192KB。),每一份又分为4KB大小的扇区,一共16个扇区,每一个扇区又分为以256个字节大小的页,可以分为16页。256个页缓存用于限制一次性写入的数据量。

左下角是SPI的控制逻辑,芯片内部进行地质所村数据读取等操作可以通过芯片自动完成,状态寄存器就是看芯片处于什么状态的,写控制逻辑,就是与WP一起实现写保护逻辑的,高电压生成器,就是配合Flash进行编程,flash是掉电不丢失,就是直接让他刻骨铭心,那么掉不掉电已经无所谓了。页地址锁存计数器,字节地址锁存计数器,用来指定地址,

image.png

 在flash中FF代表的才是空,而不是00,写入写使能就是写入写使能指令,作用是防止误操作,Flash操作并不是简单的把数据写入存储单元,这点跟ram操作有本质区别,RAM操作可以把写入的数据重新覆盖,比如先写入0XAA,在写入0X55,那么就是0X55;但是flash操作中,要遵循上面的规则,1可改为0,但是0不能改为1.所以这时候会变成0X00,所以为了避免第二点的发生,要进行第三点,写入数据之前必须先擦除,然后所有数据为变成1,这样经过变换之后可以得到何时的值。

最小擦除单元就是扇区,最大的自然就是整个芯片一起擦除了,扇区是4KB,也就是4086个字节,所以如果想单独擦除一个字节,是不可能的事情,只能整个扇区一起擦除,所以一般只能把数据读出来,然后擦除,修改之后再放回去,这显得很不方便,有优化的办法,上电之后,把数据存在RAM中,发生变动之后,把数据备份在Flash中,或者直接一个扇区一个字节。

连续写入的时候,最多只能写一页的数据,因为Flash写入很慢,而SPI频率很快,一般就是把SPI传输的数据暂时存在RAM中,也就是也缓存器中,等到SPI通信结束的时候,在把数据慢慢的Flash操作。由于也缓存器大小只能存入256个字节,所以有限制,而且其地址与Flash中的地址对应,如果从最大存储256个字节只能是从开始存储才行,不然到了最后不够大又会返回首地址进行写入,造成地址错乱,因为缓存器以及Flash空间地址是对应的。

无论是擦除还是写入操作之后,芯片都会进行一段时间的忙状态,正如上文所说,数据是先存储在缓存器中的,在这之后会进入一段时间的忙状态,搬砖,这是偶对芯片进行操作,芯片是不会响应的,所以要读取状态寄存器的BUSY位,看看是不是位0,为0才继续进行操作,1就是还在忙碌。

虽然有诸多不便,但是成本低容量大是优点,并且在非易失性存储器中,这个的传输速度也是相当快的了,正如其名,flash。

接下来就是使用手册了,自行观看,在模块资料那里。

image.png

 接线图,该接线是接在硬件引脚上面的,这样就方便进行软硬件切换,如果单纯想要软件实施,那就可以任意接。

软件实现SPIz:

在函数中建立一个MYSPI.c,主要包含通信引脚初始化封装,以及SPI的三个拼图:起始终止以及交换一个字节,这是SPI通信层的内容,然后基于SPI通信层,再建立一个独属于W25Q64的模块,该模块调用通信层的内容实现属于自己的时序功能,封装成函数,这一层叫做W25Q64的硬件驱动层,最后可以直接在主函数中被调用。

对于软件SPI,执行不可能在SS的时候同时执行MOSI或者MISO,有先后关系,先SS再执行MISO或者MOSI,而硬件SPI几乎是同时发生。

所谓移动数据,假设要把1010移动出去,只能通过交换的形式,至于SPI所谓的移出去,其实就是把相应的MOSI线或者MISO线置于与要移出位0或者1对应的高低电平罢了,假设移动1010,就把参数传进函数,然后再SS下降沿之后,就把该数与0x4(也就是最高位为1,其余为0)相与,得到1000,然后参数传入函数,转换为0或者1,然后对应的线电平置高或者低,到第二位就是与0x40>>1相与,再通过BitAction(非1即0)转换为0或者1,得到对应的位是0或者1,只有1对应的位可以检测到0或者1,其余位被屏蔽,所以这个叫做掩码。

我们这份代码烧录到的是单片机中,跟从机没关系,所以我们只管MOSI的变化就可以了,MISO的发送数据我们管不着,当然,接收数据我们也管不着(因为这个本质上是数据交换,有发送就肯定有输入,从机同样也有输入输出)。所以我们代码的任务其实就是把主机的代码发送过去,然后读取从机的代码即可,所以下面这个代码才会用到return。

uint8_t MySPI_R_MISO(void)

{

    return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);

}

uint8_t MySPI_SwapByte(uint8_t ByteSend)//这个是模式0的发送时序,其余模式也是一样,根据顺序调换位置即可,或者改变高低电平

{

    uint8_t i, ByteReceive = 0x00;

    

    for (i = 0; i < 8; i ++)

    {

        MySPI_W_MOSI(ByteSend & (0x80 >> i));//一位一位发送数据

        MySPI_W_SCK(1);

        if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//把最高位读入,如果返回不是1,就会被忽略,因为本来的数据是0x00,所以依旧是0,只有返回为1的时候,才会被修改为1.

        MySPI_W_SCK(0);

    }

    

    return ByteReceive;

}

SCLK时序变化是主机产生,SS选择从机自然也是主机产生。

C语言中的<<=1是一个复合赋值运算符,它等价于将左操作数左移1位并将结果赋值给左操作数。具体来说,它将左操作数的二进制表示形式向左移动1位,并用移位后的结果更新左操作数的值。以下是一个示例代码:

int a = 0x01; // a的二进制表示为:0000 0001

a <<= 1; // a的二进制表示为:0000 0010,等价于将a乘以2

在这个例子中,变量a被赋值为0x01,它的二进制表示为0000 0001。使用<<=1运算符,将a左移1位,并将结果赋值给a,得到变量a的值为0x02,它的二进制表示为0000 0010,相当于将a乘以2。需要注意的是,左移运算符和复合赋值运算符的优先级比较低,应该注意运算的顺序,以避免程序出错。

上面的代码可以这么优化:

优化之后就相当于把ByteSend原本的数据发出去,然后把从机的数据放在原本的这个位置,发送就是左移,把最高位发出去,接收,就是判定最低位是不是1,然后根据需要修改。

uint8_t MySPI_SwapByte(uint8_t ByteSend)

{

    for (i = 0; i < 8; i ++)

    {

        MySPI_W_MOSI(ByteSend & 0x80 );

        ByteSend<<=1;//低位会自动补0

        MySPI_W_SCK(1);

        if (MySPI_R_MISO() == 1){ByteSend |= 0x01;}

        MySPI_W_SCK(0);

    }

    return ByteSend;

}

接下来编写W25Q64的通信层,这一层要根据使用手册来确定需要指令的时序,再根据底层的时序MYIIC.c进行拼接即可。

ID的数据第一个字节是生产厂商,后两个是设备ID,高八位存储器类型,低八位表示容量,

void W25Q64_Init(void)//把引脚封装都搞好

{

    MySPI_Init();

}

void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)//读取ID号,根据数据手册,读取W25Q64的ID号先要发送一个指令,然后跟着三个字节。

{

    MySPI_Start();

    MySPI_SwapByte(W25Q64_JEDEC_ID);//把指令发送过去

    *MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//把数据读取过来,至于括号内发送过去的一般都是0xFF,没什么用,返回的是厂商号码

    *DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);//返回的是高八位数据,因为定义的数是十六位的,所以要在后续跟上左移八位的代码才行,就是高八位。

    *DID <<= 8;

    *DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);//这就是低八位

    MySPI_Stop();

}

指令码有很多,都直接写指令码令人很难读,所以要宏定义来定义指令码,提高可读性。

可以增加一个.h文件,里面写宏定义即可,然后需要则引用头文件。

image.png

 void W25Q64_WaitBusy(void)//等待状态寄存器1不忙碌,计数那个是为了防止程序卡死

{

    uint32_t Timeout;

    MySPI_Start();

    MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);

    Timeout = 100000;

    while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01)

    {

        Timeout –;

        if (Timeout == 0)

        {

            break;

        }

    }

    MySPI_Stop();

}

页编程,就是最多写入一页,所以数据最大也就是256个字节。手册中括号表示接收数据。但是在页编程最后一个,它画错了,应该是没有括号才对。由于没有24位的定义,直接就定义32位。

void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)

{

    uint16_t i;

    

    W25Q64_WriteEnable();//进行Flash操作的时候要进行写使能,这里为了方便也放写使能,就不用额外调用。写使能只能对其后的一条时序有效,只有会自动失能。

    

    MySPI_Start();

    MySPI_SwapByte(W25Q64_PAGE_PROGRAM);

    MySPI_SwapByte(Address >> 16);//32位地址,假设0x123456这里代表的是0x12

    MySPI_SwapByte(Address >> 8);//代表0x1234,但是只接受八位,所以高位舍弃,得到0x34

    MySPI_SwapByte(Address);//高位舍弃得到0x56

    for (i = 0; i < Count; i ++)

    {

        MySPI_SwapByte(DataArray[i]);//数组中存放的是要发送的数据

    }

    MySPI_Stop();//这个空闲状态的等待,它可以放在写操作之前也可以之后,不过有区别,1.前面更注重效率,因为执行完了之后可以执行别的代码,到时候回来的时候说不定已经处理完了,不用等待就再次执行 2.但是在之前有个弊端,就是需要在读操作之前也执行一次,因为在忙状态是不能进行读与写操作的,但是在之后等待空闲就好多了,不忙了才能出死循环。扇区擦除也需要。

    

    W25Q64_WaitBusy();//等待结束忙状态

}

 

手册中的dumpmy相当于无用数据,发送一个FF就行,返回的也没啥用。

读取数据,会帮你线性读取,会自动移位,同时也线性的存放在数组织中。

void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)

{

    uint32_t i;

    MySPI_Start();

    MySPI_SwapByte(W25Q64_READ_DATA);

    MySPI_SwapByte(Address >> 16);

    MySPI_SwapByte(Address >> 8);

    MySPI_SwapByte(Address);

    for (i = 0; i < Count; i ++)

    {

        DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);

    }

    MySPI_Stop();

}

由于进行写操作的时候都需要先进行写使能,如果用得到的话都要写使能

验证Flash操作的一些东西。

写操作不能跨页写,读操作却可以跨页读,下面是测试的代码。

image.png

————————————————

版权声明:本文为CSDN博主「笔下觅封侯」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:https://blog.csdn.net/m0_63148816/article/details/131253620