15、I2C读写W25Q64

一、W25Q64简介

1、W25Q64的内存空间结构: 一页256字节,4K(4096 字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048 个扇区。

2、W25Q64每页大小由256字节组成,每页的256字节用一次页编程指令即可完成。

3、擦除指令分别支持: 16页(1个扇区)、128页、256页、全片擦除。

二、电路图

1、软件模拟的SPI:线可以任意接

2、硬件模拟的SPI:要按以下方式连接

img

3、本次软件模拟和硬件模拟使用同一个电路图,方便切换

img

CS(片选):PA4 DO(从机输出):PA6

CLK(时钟):PA5 DI(从机输入):PA7

三、软件SPI读写W25Q64

1、SPI.c(初始化寄存器,实现读取一个字节的功能)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

void MySPI_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}

void MySPI_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}

uint8_t MySPI_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}


void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; //上拉输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

MySPI_W_SS(1); //SS默认高电平(下降沿为开始工作,低电平状态为工作中,上升沿为结束工作)
MySPI_W_SCK(0); //SCK默认为低电平(上升沿移入数据,下降沿移出数据)
}

void MySPI_Start(void)
{
MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
MySPI_W_SS(1);
}

//模式0
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive = 0x00;
for (i = 0; i < 8; i ++)
{
//先SS下降沿,移出数据,SCK上升沿,移入数据,再SCK下降沿,移出数据,下面只管主机,
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //将数据移出到MOSI线
MySPI_W_SCK(1); //上升沿移入数据
//当MISO为1时,置变量指定位为1,当MISO为0时,不做处理,指定位为默认的初值0
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //将移入的数据读取出来
MySPI_W_SCK(0); //下降沿移出数据
}
return ByteReceive; //读取出来的数据
}
/*
1、for循环的优化
MySPI_W_MOSI(ByteSend & 0x80 ); //将数据移出到MOSI线
ByteSend << 1; //将数据左移动1位,去掉最高位,最低位置0
MySPI_W_SCK(1); //上升沿移入数据
if (MySPI_R_MISO() == 1){ByteSend |= 0x01;} //将移入的数据读取出来,如果是0不管,如果是1,将最低位置1
MySPI_W_SCK(0); //下降沿移出数据
2、模式1
MySPI_W_SCK(1); //上升沿移出来数据
MySPI_W_MOSI(ByteSend & (0x80 >> i)); //将数据移出到MOSI线
MySPI_W_SCK(0); //下降沿移入数据
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //将移入的数据读取出来
*/

2、W25Q64.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
#include "stm32f10x.h"                  // Device header
#include "MySPI.h"
#include "W25Q64_Ins.h"

/**
* 函 数:W25Q64初始化
* 参 数:无
* 返 回 值:无
*/
void W25Q64_Init(void)
{
MySPI_Init(); //先初始化底层的SPI
}

/**
* 函 数:MPU6050读取ID号
* 参 数:MID 工厂ID,使用输出参数的形式返回
* 参 数:DID 设备ID,使用输出参数的形式返回
* 返 回 值:无
*/
void W25Q64_ReadID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_JEDEC_ID); //交换发送读取ID的指令
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收MID,通过输出参数返回
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //交换接收DID高8位
*DID <<= 8; //高8位移到高位
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE); //或上交换接收DID的低8位,通过输出参数返回
MySPI_Stop(); //SPI终止
}

/**
* 函 数:W25Q64写使能
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WriteEnable(void)
{
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_WRITE_ENABLE); //交换发送写使能的指令
MySPI_Stop(); //SPI终止
}

/**
* 函 数:W25Q64等待忙
* 参 数:无
* 返 回 值:无
*/
void W25Q64_WaitBusy(void)
{
uint32_t Timeout;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1); //交换发送读状态寄存器1的指令
Timeout = 100000; //给定超时计数时间
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01) //循环等待忙标志位
{
Timeout --; //等待时,计数值自减
if (Timeout == 0) //自减到0后,等待超时
{
/*超时的错误处理代码,可以添加到此处*/
break; //跳出等待,不等了
}
}
MySPI_Stop(); //SPI终止
}

/**
* 函 数:W25Q64页编程
* 参 数:Address 页编程的起始地址,范围:0x000000~0x7FFFFF
* 参 数:DataArray 用于写入数据的数组
* 参 数:Count 要写入数据的数量,范围:0~256
* 返 回 值:无
* 注意事项:写入的地址范围不能跨页
*/
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;

W25Q64_WriteEnable(); //写使能

MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_PAGE_PROGRAM); //交换发送页编程的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
MySPI_SwapByte(DataArray[i]); //依次在起始地址后写入数据
}
MySPI_Stop(); //SPI终止

W25Q64_WaitBusy(); //等待忙
}

/**
* 函 数:W25Q64扇区擦除(4KB)
* 参 数:Address 指定扇区的地址,范围:0x000000~0x7FFFFF
* 返 回 值:无
*/
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable(); //写使能

MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB); //交换发送扇区擦除的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
MySPI_Stop(); //SPI终止

W25Q64_WaitBusy(); //等待忙
}

/**
* 函 数:W25Q64读取数据
* 参 数:Address 读取数据的起始地址,范围:0x000000~0x7FFFFF
* 参 数:DataArray 用于接收读取数据的数组,通过输出参数返回
* 参 数:Count 要读取数据的数量,范围:0~0x800000
* 返 回 值:无
*/
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start(); //SPI起始
MySPI_SwapByte(W25Q64_READ_DATA); //交换发送读取数据的指令
MySPI_SwapByte(Address >> 16); //交换发送地址23~16位
MySPI_SwapByte(Address >> 8); //交换发送地址15~8位
MySPI_SwapByte(Address); //交换发送地址7~0位
for (i = 0; i < Count; i ++) //循环Count次
{
DataArray[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE); //依次在起始地址后读取数据
}
MySPI_Stop(); //SPI终止
}

3、main.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"

uint8_t MID; //定义用于存放MID号的变量
uint16_t DID; //定义用于存放DID号的变量

uint8_t ArrayWrite[] = {0x01, 0x02, 0x03, 0x04}; //定义要写入数据的测试数组
uint8_t ArrayRead[4]; //定义要读取数据的测试数组

int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
W25Q64_Init(); //W25Q64初始化

/*显示静态字符串*/
OLED_ShowString(1, 1, "MID: DID:");
OLED_ShowString(2, 1, "W:");
OLED_ShowString(3, 1, "R:");

/*显示ID号*/
W25Q64_ReadID(&MID, &DID); //获取W25Q64的ID号
OLED_ShowHexNum(1, 5, MID, 2); //显示MID
OLED_ShowHexNum(1, 12, DID, 4); //显示DID

W25Q64_SectorErase(0x000000); //扇区擦除
/*
验证扇区擦除功能(方法:注释掉这一句)
如果不擦除,一开始写入AA、BB、CC、DD,后面再次写入55、66、77、88,则读出来00、22、44、88
即如果不进行擦除,则读出的数据=原始数据&写入的数据
*/
W25Q64_PageProgram(0x000000, ArrayWrite, 4); //将写入数据的测试数组写入到W25Q64中
W25Q64_ReadData(0x000000, ArrayRead, 4); //读取刚写入的测试数据到读取数据的测试数组中
/*
数据是不能跨页写入
验证:
若写入55 66 77 88
W25Q64_PageProgram(0x0000FF, ArrayWrite, 4); //数据不能跨页写入,66 77 88返回到页首写入
W25Q64_ReadData(0x0000FF, ArrayRead, 4); //读取数据可以跨页读出
则读出的是55 FF FF FF ,FF为第二页的数据,第二页是擦除了的,没有写入,默认是FF
W25Q64_PageProgram(0x0000FF, ArrayWrite, 4);
W25Q64_ReadData(0x000000, ArrayRead, 4); //读出:66 77 88 FF
*/

/*显示数据*/
OLED_ShowHexNum(2, 3, ArrayWrite[0], 2); //显示写入数据的测试数组
OLED_ShowHexNum(2, 6, ArrayWrite[1], 2);
OLED_ShowHexNum(2, 9, ArrayWrite[2], 2);
OLED_ShowHexNum(2, 12, ArrayWrite[3], 2);

OLED_ShowHexNum(3, 3, ArrayRead[0], 2); //显示读取数据的测试数组
OLED_ShowHexNum(3, 6, ArrayRead[1], 2);
OLED_ShowHexNum(3, 9, ArrayRead[2], 2);
OLED_ShowHexNum(3, 12, ArrayRead[3], 2);

while (1)
{

}
}

四、硬件读写I2C

只需要在软件的基础上添加以下的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
#include "stm32f10x.h"                  // Device header

void MySPI_W_SS(uint8_t BitValue)//SS还是软件模拟
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}

/*
初始化步骤
1、开启时钟
2、初始化GPIO口
(1)SCK、MOSI是由硬件外设控制的输出信号:复用推挽输出
(2)MISO是硬件外设的输入信号:上拉输入(输入设备可以有很多个,不存在复用输入)
(3)SS为软件控制的输出信号,配置为通用推挽输出
3、配置SPI外设: 用结构体
4、开关控制:调用SPI_Cmd,给SPI使能
*/
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);

GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;//通用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //双线全双工
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8位数据帧
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //高位先行
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128; //波特率预分频器,配置SCK时钟的频率.SPI1:72MHz/128,SPI2:36MHz/128
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //默认低电平,空闲默认低电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //第一个边沿开始采样(移入)
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //一般选择软件NSS模式(不用了解)
SPI_InitStructure.SPI_CRCPolynomial = 7; //随便填
SPI_Init(SPI1, &SPI_InitStructure);

SPI_Cmd(SPI1, ENABLE);

MySPI_W_SS(1);//默认不选中从机
}

void MySPI_Start(void)
{
MySPI_W_SS(0);
}

void MySPI_Stop(void)
{
MySPI_W_SS(1);
}


/*
等待TXE为1,发送寄存器为空,发送寄存器不为空,先不着急写
过程:
写入数据到TDR-->转移到移位寄存器——>一旦移位寄存器有数据,时序波形就会自动产生(则ByteSend就会通过MOSI一位一位地移出去)
——>在MOSI线上,就自动产生发送的时序波形
由于是非连续传输,时序产生的时间内,不必提前把下个数据放到TDR,直接等待这段时间过去就行
在发送的同时,MISO会移位进行接收
发送和接收是同步
接收移位完成时,会收到一个字节数据这时会置标志位置RXNE
*/
/*
步骤总结(完成一个字节的交换)
1、等待TXE为1
2、写发送的数据至TDR,一旦TDR写出数据来。时序就会自动生成
3、等待RXNE为1,发送完成,即接收完成,RXNE置1
4、读取RDR接收的数据,就是置换接收的一个字节
注意:(1)必须是发送,同时接收,要先写东西,触发时序
(2)根据手册,
发送缓冲器空闲标志(TXE):写入DR时,会顺便执行清楚TXE的操作,无须手动清除
接收缓冲器非空(RXNE):读取SPI数据寄存器可以清除此标志
*/
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) != SET);//若没有数据,则MOSI线为SET

SPI_I2S_SendData(SPI1, ByteSend);//ByteSend为要写入到DR,即TDR的数据(要发送的数据),之后会转入到移位寄存器

while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//接收数据完毕的时候,MISO线有标志位RXNE

return SPI_I2S_ReceiveData(SPI1);//读取DR,从RDR中,把交换接收的数据读取出来,返回值为RDR接收的数据
}