主设备负责发起通信请求,从设备负责响应,从设备通过 SCK 引脚得到主设备提供的时钟信号,从而使得主、从设备均使用同一个时钟进行同步的全双工通信。对于从设备而言,MOSI 引脚输入来自主设备的发送数据,MISO 引脚输出响应数据传给主设备。
SPI 需要根据从机特性,选配时钟极性(CPOL)和时钟相位(CPHA),比如从机时钟空闲时为低电平,且在时钟信号的第一个跳变沿采样,此时时序见下图:
二、W25Q80介绍
W25Q80是一种串行闪存存储器,主要特点包括:
- 容量:8 Mbit(1 MB)的存储容量,可以存储大量数据。
- SPI接口:采用SPI接口进行数据通信,具备高速的数据传输能力。
- 快速读取:支持快速的连续读取操作,可提供高效的读取性能。
- 块擦除:支持块擦除功能,可以批量擦除数据,提高擦除效率。
- 低功耗:采用低功耗设计,适用于对功耗要求较高的应用场景。
W25Q80可以划分为 4096 个扇区,每个扇区包含 256 个页,每个页的大小为 256 字节。
下图为W25Q80的指令:
三、例程
1.介绍
本例程实现SPI与W25Q80通信,SPI查询W25Q80的设备号,并全片擦除后写入数据,再读取出来,看写入的数据与读取的数据是否一致。
2.编码详情
(1)初始化配置SPI,速度为PCLK1(36MHz)的32分频,约为1.125MHz;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_DataWidth = 8;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_32;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI2, &SPI_InitStructure);
exSPI_DataEdgeAdjust(SPI2, SPI_DataEdgeAdjust_FAST);
SPI_BiDirectionalLineConfig(SPI2, SPI_Direction_Tx);
SPI_BiDirectionalLineConfig(SPI2, SPI_Direction_Rx);
SPI_Cmd(SPI2, ENABLE);
(2)编写SPI发送和读取数据的函数;
uint32_t writeAndReadData(uint8_t data)
{
SPI_SendData(SPI2, data);
while (1) {
if(SPI_GetFlagStatus(SPI2, SPI_FLAG_TXEPT)) {
break;
}
}
while (1) {
if(SPI_GetFlagStatus(SPI2, SPI_FLAG_RXAVL)) {
return SPI_ReceiveData(SPI2);
}
}
}
(3)获取W25Q80的设备ID;
SPI_CSInternalSelected(SPI2, ENABLE); // 片选
writeAndReadData(0x9F); // 读ID指令
ID |= writeAndReadData(0x00) << 16;
ID |= writeAndReadData(0x00) << 8;
ID |= writeAndReadData(0x00);
SPI_CSInternalSelected(SPI2, DISABLE); // 取消片选
printf("\n\nread device id: %X", ID);
(4)全片擦除W25Q80,并检查状态等待擦除完成;
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x06); // 写使能
SPI_CSInternalSelected(SPI2, DISABLE);
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0xC7); // 全片擦除指令
SPI_CSInternalSelected(SPI2, DISABLE);
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x05); // 读状态寄存器
while(1) {
temp = writeAndReadData(0x00);
if((temp & 0x01) == 0x0) // 擦除完成
break;
}
SPI_CSInternalSelected(SPI2, DISABLE);
printf("\n\nerase complete");
(5)写数据,并检查状态等待写完;
for (i = 0; i < 256; i++) txData[i] = i;
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x06);// 写使能
SPI_CSInternalSelected(SPI2, DISABLE);
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x02); // 页编程
writeAndReadData(0x00); // 页地址
writeAndReadData(0x00);
writeAndReadData(0x00);
for (i = 0; i < 256; i++)
{
writeAndReadData(txData[i]);
}
SPI_CSInternalSelected(SPI2, DISABLE);
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x05); // 读状态寄存器
while(1) {
temp = writeAndReadData(0x00);
if((temp & 0x01) == 0x0) // 页编程完成
break;
}
SPI_CSInternalSelected(SPI2, DISABLE);
printf("\n\npage programm complete");
(6)读数据,并打印出来。
SPI_CSInternalSelected(SPI2, ENABLE);
writeAndReadData(0x03); // 读数据
writeAndReadData(0x00); // 页地址
writeAndReadData(0x00);
writeAndReadData(0x00);
for (i = 0; i < 10; i++)
{
rxData[i] = writeAndReadData(0x00);
}
SPI_CSInternalSelected(SPI2, DISABLE);
printf("\n\nread data completely\n");
for (i = 0; i < 10; i++)
printf("\nrxDate[%d] = %d", i, rxData[i]);
3.串口输出结果
下图为串口输出数据,基本符合程序流程和结果。
4.逻辑分析仪
下图为逻辑分析仪截取的发送接收图片,可以清楚的看到当前SCK速度接近1.125MHz,以及发送数据也符合预期。