【标准库】w25q64与SPI软件通信

文章详细介绍了SPI接口的工作原理,包括地址空间、数据传输速率和模式,以及W25Q64Flash存储器的结构、操作机制,如写保护、时序控制、擦除和写入过程。此外,还讨论了软件实现SPI通信的方法和注意事项。
摘要由CSDN通过智能技术生成

 

xx代表数值。

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

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

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

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

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

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

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

w25q64框图

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

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

 在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。

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

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

软件实现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文件,里面写宏定义即可,然后需要则引用头文件。

 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操作的一些东西。

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

W25Q64是一款闪存芯片,它通常用于嵌入式系统中提供非易失性的数据存储。SPI(Serial Peripheral Interface,串行外围接口)是一种并行到串行的通信协议,用于连接微控制器和其他外围设备,如W25Q64这样的存储器。 软件SPI和硬件SPI的主要区别在于数据传输的控制和实现方式: 1. **软件SPI:** - **控制在软件中:**在这种模式下,微控制器的CPU负责整个SPI通信过程,包括时钟信号的生成、数据发送和接收的控制逻辑。软件SPI需要更多的CPU资源,并可能导致系统性能降低,尤其是在数据传输速率较高的情况下。 - **灵活性较低:**由于CPU处理,对传输速度有限制,且不能同时执行其他任务。 2. **硬件SPI:** - **硬件支持:**硬件SPI使用专门的硬件模块(如专用SPI控制器或GPIO端口的集成功能)来处理SPI通信,减轻了CPU的工作负担。 - **速度更快:**由于硬件加速,硬件SPI能实现更高的数据传输速率,使得CPU能进行其他并行任务,提高系统效率。 - **可扩展性和可靠性:**硬件SPI设计通常更稳定,不易受到干扰,适合于需要大量并发通信的场景。 3. **配置和驱动:** - 软件SPI可能需要编写复杂的软件驱动程序来管理SPI功能,而硬件SPI通常有现成的驱动支持,更容易集成。 - 硬件SPI可能具有固定的通信模式和参数,不如软件灵活,但对新手开发者来说更加直观。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值