打印
[STM32F1]

STM32基础篇FLASH 实验-SPI

[复制链接]
2701|38
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
试验目标:
1、学会 STM32 硬件 SPI
2、学会对 EN25Q64 进行读写操作

沙发
aizaixiyuanqian|  楼主 | 2018-2-25 19:27 | 只看该作者
EN25Q64 简介
EN25Q64 是华邦公司推出的大容量 SPI FLASH 产品,EN25Q64 的容量为
64M 比特,也就是说有 8M 字节。EN25Q64 将 8M 的容量分为 128 个块(Block),
每个块大小为 64K 字节,每个块又分为 16 个扇区(Sector),每个扇区 4K 个
字节。EN25Q64 的最少擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。

使用特权

评论回复
板凳
aizaixiyuanqian|  楼主 | 2018-2-25 19:27 | 只看该作者
EN25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可
以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320M),更多的
EN25Q64 的介绍,请参考 EN25Q64 的 DATASHEET。

使用特权

评论回复
地板
aizaixiyuanqian|  楼主 | 2018-2-25 19:28 | 只看该作者
SPI 简介
从上面的简介我们知道,EN25Q64 是使用 SPI 来通信的。那什么是 SPI 呢?
SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接
口,SPI 接口主要用四根线进行通信

使用特权

评论回复
5
aizaixiyuanqian|  楼主 | 2018-2-25 19:32 | 只看该作者
1、MISO:主设备数据输入,从设备数据输出。
2、MOSI:主设备数据输出,从设备数据输入。
3、SCLK:时钟信号,由主设备产生。
4、CS:从设备片选信号,由主设备控制。

使用特权

评论回复
6
aizaixiyuanqian|  楼主 | 2018-2-25 19:37 | 只看该作者
而通常意义上,SPI 的通信只用三根线就可以了,一根时钟线、一根输出、
一根输入。为了更好理解 SPI 的传输原理,我们来看一下 SPI 的内部结构

使用特权

评论回复
7
aizaixiyuanqian|  楼主 | 2018-2-25 19:37 | 只看该作者
SPI 数据的传输过程其实是通过一个移位寄存器来完成
的,主机将自己的移位寄存器的数据移出,同时从机的移位寄存器数据移入,同
时将自己的数据移出。简单的来理解,就像将两个寄存器贴在一起,然后进行循
环左移或者循环右移(SPI 的传输可以选择先发送高位还是先发送低位。),直到
两个寄存器的数据交换为止。而时钟信号 SCLK 就是控制传输速率的。

使用特权

评论回复
8
aizaixiyuanqian|  楼主 | 2018-2-25 19:38 | 只看该作者
STM32 内部是给我们提供了一个 SPI 的外设的,那么我们就可以使用单片机
的内部的 SPI 来控制 EN25Q64 了。

使用特权

评论回复
9
aizaixiyuanqian|  楼主 | 2018-2-25 20:05 | 只看该作者
EN25Q64 的原理图

使用特权

评论回复
10
aizaixiyuanqian|  楼主 | 2018-2-25 20:08 | 只看该作者

上面的原理图,我们发现,其实 EN25Q64 是使用了 STM32 单片机的 SPI2
的接口(我们开发板上面使用的单片机是拥有两个 SPI 外设的。),而 EN25Q64
的片选控制端使用的是 PG13。

使用特权

评论回复
11
aizaixiyuanqian|  楼主 | 2018-2-25 20:08 | 只看该作者
1.打开时钟使能。
我们要使用 GPIOB、GPIOG 和 SPI 所以要打开三个时钟。
代码为:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOG, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

使用特权

评论回复
12
aizaixiyuanqian|  楼主 | 2018-2-25 20:08 | 只看该作者
2、配置 GPIO 口的模式。
这这里我们要打开 GPIOG 和 GPIOB 的时钟,然后将片选端 PG13 设置
为推挽输出,然后设置 SPI 使用的 GPIO 口,将 PB13、PB14 和 PB15 设置
为复用推挽输入。大家可能疑惑为什么 PB14 也设置为输出呢?它不是应该
输入吗?在这里它是作为复用端口了,所以设置为复用推挽输出就是跟外设
连接,要外设自己来决定输入输出了。
那么我们要使用的库函数大家都很熟悉了,就不详细介绍了。
代码为:
/* flash_cs 的 IO 口设置 PG13 */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOG, &GPIO_InitStructure);
/* SPI 的 IO 口设置 */
GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_13  |  GPIO_Pin_14  |
GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

使用特权

评论回复
13
aizaixiyuanqian|  楼主 | 2018-2-25 21:14 | 只看该作者
本帖最后由 aizaixiyuanqian 于 2018-2-25 21:16 编辑

设置 SPI 参数。
1) SPI_Init ()函数
这个函数可以用来初始化 SPI 外设。它有两个参数:
第一个参数:选择你要设置的 SPI,我们要使用 SPI2 所以设置为:SPI2。
第二个参数:传递一个结构体,用于设置 SPI 的参数。它一共有 9个成员:
SPI_Direction:表示 SPI 的方向,我们要使用的是双线双向全双工,所以我们设置为:SPI_Direction_2Lines_FullDuplex。
SPI_Mode:表示 SPI 的模式,我们使用主机模式,所以设置为:SPI_Mode_Master。
SPI_DataSize:表示传输数据的长度,我们使用 8 位数据传输,所以设置为:SPI_DataSize_8b。
SPI_CPOL:表示在空闲的时候时钟线是高电平还是低电平。我们选择高:SPI_CPOL_High。
SPI_CPHA:选择采样延迟。我们选择从第二个时钟开始采样,所以设置为:SPI_CPHA_2Edge。
SPI_NSS:表示是否用硬件控制片选端。我们的片选另外使用的PG13,所以不用。所以设置为软件控制:SPI_NSS_Soft。
SPI_BaudRatePrescaler:表示时钟波特率的分频,用来设置 SPI的速度。我们设置为 256 分频:SPI_BaudRatePrescaler_256。
SPI_FirstBit:表示 SPI 的第一位,我们使用先发送高位,所以设置为:SPI_FirstBit_MSB。
SPI_CRCPolynomial:表示 CRC 效验的设置,我们使用 7 位 CRC,所以设置为:7。

使用特权

评论回复
14
aizaixiyuanqian|  楼主 | 2018-2-25 21:16 | 只看该作者
所以代码为:
SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//选择全双工 SPI 模式
SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //主机模式
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //8 位 SPI
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High; //时钟悬空高电平
SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge; //在第二个时钟采集数据
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //Nss 使用软件控制
/* 选择波特率预分频为 256 */
SPI_InitStructure.SPI_BaudRatePrescaler  =
SPI_BaudRatePrescaler_256;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;//从最高位开始传输
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure);

使用特权

评论回复
15
aizaixiyuanqian|  楼主 | 2018-2-25 21:17 | 只看该作者
打开 SPI 的使能。
我们可以使用 SPI_Cmd()函数。它有两个参数:
第一个参数选择要设置的 SPI,我们选择 SPI2。
第二个参数用来设置 SPI 的状态,我们选择 ANABLE。

使用特权

评论回复
16
aizaixiyuanqian|  楼主 | 2018-2-25 21:17 | 只看该作者
使用 SPI 读写。
从上面 SPI 的简介我们知道,SPI 发送的同时,也接收数据。所以我们
使用 SPI 读写的时候,发送的同时也接收。
1) SPI_I2S_SendData()函数。
这个函数用来发送数据的,它有两个参数:
第一个参数选择要使用的 SPI,我们选择 SPI2。
第二个参数用来传递我们要发送的 8 位数据。
2) SPI_I2S_GetFlagStatus()函数
这个函数用来检测 SPI 的状态,一般在发送和接收之前都要检测 SPI的状态,发送时要检测 SPI 的发送缓冲器是否为空,接收时检测接收缓冲器是否为非空。这个函数有两个参数:
第一个参数选择要检测的 SPI,我们选择 SPI2。
第二个参数用来传递我们要检测的状态。检测发送缓冲器为空的时候设置为:SPI_I2S_FLAG_TXE;检测接收缓冲器是否为非空的时候设置为:SPI_I2S_FLAG_RXNE。
同时它返回一个状态值,状态出现返回:SET(非零);否者返回:RESET(0)。

使用特权

评论回复
17
aizaixiyuanqian|  楼主 | 2018-2-25 21:18 | 只看该作者
所以使用 SPI 读写我们可以写为:
uint8_t SPI2_WriteReadData(uint8_t dat)
{
uint16_t i = 0;
/* 当发送缓冲器空 */
while(SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET)
{
i++;
if(i > 10000)
{
eturn 0xFF;
}
}
/* 发送数据 */
SPI_I2S_SendData(SPI2, dat);
/* 等待接收缓冲器为非空 */
while(SPI_I2S_GetFlagStatus(SPI2,  SPI_I2S_FLAG_RXNE)  ==
RESET);
/* 将读取到的数值返回 */
return SPI_I2S_ReceiveData(SPI2);
}

使用特权

评论回复
18
aizaixiyuanqian|  楼主 | 2018-2-25 21:19 | 只看该作者
我们知道了,怎么跟 EN25Q64 通信之后,我们首先要对 EN25Q64 进行
初始化,一般来说 EN25Q64 是不需要什么初始化的,不过我们为了确定芯片,
我们先来读取一次芯片的 ID。
从上面的命令表,我们知道读取芯片 ID 的命令是 90H,命令发送的第 1
个字节是命令字节,然后第 2、3、4 字节是要么是伪字节要么是无用字节,
在第 5 个字节返回生产商 ID(即 M7-M0),第 6 个字节返回器件 ID(即
ID7-ID0),我们使用的是 EN25Q64,生产商 ID 是 0x1C,器件 ID 是 0x16,
合起来的 ID 就是 0x1C16

使用特权

评论回复
19
aizaixiyuanqian|  楼主 | 2018-2-25 21:20 | 只看该作者
读取芯片的操作步骤如下:
1) 把 CS 引脚拉低,选择片选。
2) 把指令 90H 发送到芯片。
3) 发送 3 个无用字节。
4) 读取生产商 ID
5) 读取器件 ID
6) 把 CS 引脚拉高,取消片选。

使用特权

评论回复
20
aizaixiyuanqian|  楼主 | 2018-2-25 21:20 | 只看该作者
代码我们可以写为:
uint16_t FLASH_ReadID(void)
{
uint16_t id = 0;
FLASH_CS_CLR; //打开片选
SPI2_WriteReadData(0x90);//发送读取 ID 命令
SPI2_WriteReadData(0x00);
SPI2_WriteReadData(0x00);
SPI2_WriteReadData(0x00);
id |= SPI2_WriteReadData(0xFF) << 8; //读取 16 位 ID
id |= SPI2_WriteReadData(0xFF);
FLASH_CS_SET;  //关闭片选
return id;
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

62

主题

1353

帖子

6

粉丝