STM32--软件SPI读写W25Q64

上次我们了解了W25Q64芯片,这次我们软件SPI读写。

一.硬件接线电路

二.程序编写

1.程序准备

复制一下OLED显示函数,进行修改

先编译一下,方便我们代码有提示。

快速添加好两个模块。

还有我们的.h文件。

2.引脚初始化

复制一下LED的初始化,然后我们进行修改。

对于主机来说,时钟,片选,主机输出,都是输出引脚,剩下一个是主机输入,是输入引脚。所以这一个角是浮空或上拉,我们选择上拉就可以了。

这个DO就是从机输出,也就是主机输入,所以PA6为上拉模式,剩下的为推完模式。

这样引脚初始话就完成了

3.重新封装函数。

(1)选择外设函数

SS对应的是PA4引脚

BitAction就是这个类型。

(2)时钟函数

CLK就是SCK对应的是PA4引脚

(3)主机输出从机输入

MOSI对应的是PA7引脚

(4)主机输入从机输出

直接返回PA6口电平

这样我们4个SPI的通信引脚,我们就包装换好名字了。

(5)初始化后默认电平设置

初始化之后置引脚电平,SS置高电平,默认不选中从机,SCK计划使用模式0,所以初始化为低电平,MOSI没有明确规定可以不管,MISO是输入引脚不用输出电平。

三.SPI函数编写

1.起始信号

直接把SS置低电平就可以了。

2.终止信号

3.交换一个字节

(1)模式0

交换一个字节  这个ByteSend是我们传进来的参数,要通过交换一个字节发送出去,返回值是ByteReveive是通过交换一个字节收到的数据。通过返回值返回到调用函数的地方。

先定义一个接收变量。之后返回回去,在这中间我们来完成时序。

如图SS下降沿和数据转移几乎是同时发生的,但是并不是,需要先下降沿再转移数据,这个下降沿是触发数据移出这个动作的条件,先有了下降沿,才会有数据移出这个条件。对于硬件SPI来说,因为使用了硬件的移位寄存器电路,所以这两个动作几乎是同时发生的。对于软件SPI来说,由于软件执行程序是一条一条执行的,我们不可能同时完成两个动作,所以软件SPI我们直接把它看成先后执行的逻辑。

逻辑就是先SS下降沿,先把数据移出,再SCK上升沿,移入数据。再SCK下降沿再移出数据。以这个具有先后顺序的逻辑来执行,这样才能对应一条条依次执行的代码。

写MOSI最高位  0X80= 1000 0000 与的话就是提取最高位

写SCK为1,产生上升沿,让从机读走数据

主机的任务是上升沿后,把这个从机放到MISO的数据读取,把这个位或上去这样子最高位就是1,就是读到的数据了。

然后我们用一个循环套起来,就可以实现SPI模式0交换一个字节的时序了。

然后这个程序目前是用0x80>>i位的方式,依次取出ByteSend的每一位,或者依次给ByteReceive的每一位置1这个0x80右移i的作用就是用来挑出数据的某一位或者某几位。就是用来屏蔽其他位,那么就可以把这种类型的数据叫做掩码。

我们可以优化一下程序。

就是把ByteSend最高位移到MOSI,ByteSend移出了一位,他的最低位会自动补0。

把收到数据放在 ByteSend最低位,然后循环。就不需要ByteReveive这个变量了。这里我们暂时使用第一种方法,因为第二种的BYTESEN传输之后改变了。

(2)修改为模式1

只需要改一下相位就可以了。

(3)修改为模式3

再模式1的基础上,我们就可以把所有出现SCK时钟的地方,0变为1,1变为0。就可以了。

(4)修改为模式2

再模式0的基础上,我们就可以把所有出现SCK时钟的地方,0变为1,1变为0。就可以了。

4.声明函数

这四个函数,一个初始化,三个SPI时序基本单元,

编译一下。

四.W25Q64驱动函数编写

1.新建模块

快速填写.h文件。

调用MySPI头文件。

初始化调用一下我们就要实现业务代码了。拼接完整的时序,主要参考手册的指令集部分。

2.读取ID号验证SPI时序

我们先写SPI读取ID号的时序,然后进行判断,看看SPI通信是否错误。

读取ID号的时序就是,先起始,然后交换发送指令9F,随后连续交换接收3个字节,停止,我该怎么知道,哪个是交换发送,哪个是交换接收呢。

手册中写到,圆括号括起来的,就是我们需要交换接收的数据。这里的三个字节都是括起来的,所以这三个字节都需要我们交换接收。

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

我们计划这个函数是有两个返回值的,我们依旧使用指针来实现多返回值,原来的返回值就不要了

开始传输

写入要传输字节开始,交换第一个字节 ,返回值是交换接收的,因为交换接受的没有意义,所以这里我们就不要了。

然后把数据存在指针变量里面,高八位和低八位分开存。第二次读取变为或等于。不能直接写等于,否则高八位置0了。

最后加上结束。

3.小测试

声明函数。

引用头文件。

初始化。

定义两个变量

把ID号和厂商ID的地址传送过去。等函数执行完,ID号我们就拿到了。

用OLED显示一下。

编译下载。

可以看到屏幕上显示了两个数,厂商ID是EF,设备ID是4017。

对照手册,厂商ID是EF,设备ID我们用的是9F指令,所以读取的就是4017。

4.封装指令函数

然后我们,把指令函数封装一下

新建一个指令模块。

快速写好.h文件然后进行宏定义。

#define W25Q64_WRITE_ENABLE                            0x06
#define W25Q64_WRITE_DISABLE                        0x04
#define W25Q64_READ_STATUS_REGISTER_1                0x05
#define W25Q64_READ_STATUS_REGISTER_2                0x35
#define W25Q64_WRITE_STATUS_REGISTER                0x01
#define W25Q64_PAGE_PROGRAM                            0x02
#define W25Q64_QUAD_PAGE_PROGRAM                    0x32
#define W25Q64_BLOCK_ERASE_64KB                        0xD8
#define W25Q64_BLOCK_ERASE_32KB                        0x52
#define W25Q64_SECTOR_ERASE_4KB                        0x20
#define W25Q64_CHIP_ERASE                            0xC7
#define W25Q64_ERASE_SUSPEND                        0x75
#define W25Q64_ERASE_RESUME                            0x7A
#define W25Q64_POWER_DOWN                            0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE                0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET            0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID        0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID                0x90
#define W25Q64_READ_UNIQUE_ID                        0x4B
#define W25Q64_JEDEC_ID                                0x9F
#define W25Q64_READ_DATA                            0x03
#define W25Q64_FAST_READ                            0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT                0x3B
#define W25Q64_FAST_READ_DUAL_IO                    0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT                0x6B
#define W25Q64_FAST_READ_QUAD_IO                    0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO                0xE3

#define W25Q64_DUMMY_BYTE                            0xFF

我们进行一下复制。

引用头文件。

(1)写使能函数

定义一个写使能的函数。

这样就可以了。

(2)读状态寄存器1

我们读状态寄存器的原因就是看看芯片在不在忙。

我们要读取他的最低位,BUSY看看是不是还在忙,在忙的话就是1,不忙了就是0。

手册中写道,这个状态寄存器可以被连续读出,这个连续读出的特性,就方便我们进行等待操作。

所以这就是我们的等待函数。加一个break是为了跳出循环。防止程序卡死

(3)发送页函数编写。

函数我都会注释出来用处。最后来一个结束函数。

我们调用这个函数,给一个指定的起始的地址,再给一个要写入的数组和数量,他就能帮我们写入数据了。

(3)扇区擦除

调用这个函数,所在地址的扇区就会被擦除。

(4)读取数据

我们调用这个函数,给一个指定的起始地址再给接收数据的数组和数量,他就能帮我们读取数据芯片了。

我们在页编程和擦除数据之前,给一个写使能,就不用到时候我们再去写使能了。这样我们在调用写使能的函数,这样我们调用写入的函数,就不需要写使能了。

这个写使能仅对之后跟随的一条时序有效。所以我们在每次写入之前都加一条写使能。

之后我们还需要完成等待,可以进行事前等待和事后等待,事前等待就是在每次要写入之前,进行等待,事后就是,在每次写入之后进行等待,结束了在退出函数。这两者的区别就是,事后等待更保险,在函数之外的地方,芯片肯定是不忙的。事前等待效率会高一些。可能等待时候执行别的代码,回来等待完成了。事后等待只需要在写入后调用就行,事前等待,需要在写入操作和读取操作之前,都需要调用。因为芯片忙的时候,读取也是不可以的。如果采取事前等待,那么读取操作之前,就需要等待。这就是两中等待方式的区别。

我们在页编程之后,实行等待。

之后扇区擦除也来一个等待。

(5)测试

把三个函数放在头文件声明一下。

定义要发送和接收的数组。

OLED显示的ID号和数据都换个位置。

下载就是这样子。

然后我们写入数据并且显示出来。

在显示一下读出来的。如果没用问题的话,写入的和读取的都会是一样的数据。

编译下载。

显示没用问题。

修改一下写入数组。

读出A1,B2,C3,D4,也没有问题。

我们把擦除和写入的代码注释掉

数据目前没用变化。

断电重启。

读取的仍外元数据

我们解除擦除的数据,只擦除不写入。

可以看到读取的数据全是FF。说明flash擦除之后都是FF。

我们验证一下flash只能1写0,不能0写1的特点

这是有擦除的写入。

把擦除注释掉,然后只更改不擦除。

直接更改为新数据。

可以看到写入的和读出的数据不一样。如果不擦除,读出的数据=原始数据&写入的数据(按位与)。

我们再来看一下写入数据不能跨页的现象,上次我们知道,页地址范围是xxxx00~xxxxFF。所以最后两个数是页内地址,前面是页地址

我们直接从第一页最后写,看看能不能跨页写道下一页。

读取数据也从FF开始读。

可以看到写入的数据到了写出的,就只有一位起作用。这说明写入数据没用跨页,这个55写入的地址是0FF后面的66并没有跨越到下一个地址100。而是返回第一页页首,000了。又因为读取时可以跨页的所以读取到的FF是第二页的数据。第二页是擦出了的,没有写入,所以默认是FF。

再把读取,改到前一个地址00,看看之前写入的,是不是在第一个字节的起始位置。

可以看到果然这三个字节是在第一页起始地址位置。这就是我们flash写入不能跨页的现象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值