一、W25Q64简介
1、W25Q64的内存空间结构: 一页256字节,4K(4096 字节)为一个扇区,16个扇区为1块,容量为8M字节,共有128个块,2048 个扇区。
2、W25Q64每页大小由256字节组成,每页的256字节用一次页编程指令即可完成。
3、擦除指令分别支持: 16页(1个扇区)、128页、256页、全片擦除。
二、电路图
1、软件模拟的SPI:线可以任意接
2、硬件模拟的SPI:要按以下方式连接
3、本次软件模拟和硬件模拟使用同一个电路图,方便切换
CS(片选):PA4 DO(从机输出):PA6
CLK(时钟):PA5 DI(从机输入):PA7
三、软件SPI读写W25Q64
1、SPI.c(初始化寄存器,实现读取一个字节的功能)
#include "stm32f10x.h" // Device headervoid 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时,不做处理,指定位为默认的初值0if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);} //将移入的数据读取出来MySPI_W_SCK(0); //下降沿移出数据}return ByteReceive; //读取出来的数据
}
/*1、for循环的优化MySPI_W_MOSI(ByteSend & 0x80 ); //将数据移出到MOSI线ByteSend << 1; //将数据左移动1位,去掉最高位,最低位置0MySPI_W_SCK(1); //上升沿移入数据if (MySPI_R_MISO() == 1){ByteSend |= 0x01;} //将移入的数据读取出来,如果是0不管,如果是1,将最低位置1MySPI_W_SCK(0); //下降沿移出数据2、模式1MySPI_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
#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
#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); //显示MIDOLED_ShowHexNum(1, 12, DID, 4); //显示DIDW25Q64_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为第二页的数据,第二页是擦除了的,没有写入,默认是FFW25Q64_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
只需要在软件的基础上添加以下的代码
#include "stm32f10x.h" // Device headervoid 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/128SPI_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为12、写发送的数据至TDR,一旦TDR写出数据来。时序就会自动生成3、等待RXNE为1,发送完成,即接收完成,RXNE置14、读取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线为SETSPI_I2S_SendData(SPI1, ByteSend);//ByteSend为要写入到DR,即TDR的数据(要发送的数据),之后会转入到移位寄存器while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) != SET);//接收数据完毕的时候,MISO线有标志位RXNEreturn SPI_I2S_ReceiveData(SPI1);//读取DR,从RDR中,把交换接收的数据读取出来,返回值为RDR接收的数据
}