目录
软件SPI读写W25Q64
MySPI
W25Q64
主函数
硬件SPI读写W25Q64
SPI模块包含通信引脚封装,初始化,SPI三个基本时序单元(起始,终止,交换一个字节)
W25Q64通过调用SPI基本时序完成整个时序(写使能,页编程,擦除,读数据)
主函数中调用W25Q64的硬件驱动层函数时序功能
#include "stm32f10x.h" // Device header
void MySPI_W_CS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_W_CLK(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_Init_Structure;
//要求配置为推挽输出,浮空或上拉输入
GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Init_Structure);
GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4|GPIO_Pin_5|GPIO_Pin_7;
GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Init_Structure);
MySPI_W_CS(1);
MySPI_W_CLK(0);
}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
MySPI_W_CS(1);
}
//通过掩码,依次挑出每一位操作,优点是保留了原数据的值
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
uint8_t i,ByteReceive = 0x00;
for(i = 0;i < 8;i ++)
{
MySPI_W_MOSI(ByteSend &(0x80>>i));
MySPI_W_CLK(1);
if(MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_CLK(0);
}
return ByteReceive;
}
//还可以按照移位示意图中的方式交换数据,优点是效率高,但不能保存原数据值
//uint8_t MySPI_SwapByte(uint8_t ByteSend)
//{
// uint8_t i;
// for(i=0;i<8;i ++)
// {
// MySPI_W_MOSI(ByteSend &0x80);
// ByteSend <<=1; //最高位移出,最后补0
// MySPI_W_CLK(1);
// if(MySPI_R_MISO() == 1) {ByteSend |= 0x01;} //输入的数据放在最低位
// MySPI_W_CLK(0);
// }
// return ByteSend;
//}
在注意事项中:写入操作后,芯片进入忙状态。这里通过定义的函数进行事前等待和事后等待。
事前等待:1、写入前先等待,等不忙了再写入 2、效率高。 3、在写入和读取操作之前都要等待。
事后等待:1、写入后立刻等待,不忙了退出。 2、这样最保险,函数结束后,函数之外的地方芯片肯定不忙。 3、只需在写入后等待。
#include "stm32f10x.h" // Device header
#include "MySPI.H"
#include "W25Q64_INS.H"
void W25Q64_Init(void)
{
MySPI_Init();
}
//用指针的方式来获取多个函数的值!!!
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_JEDEC_ID);
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WiteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
/**
* @brief 直到BUSY清零后结束
* @param
* @retval
*/
void W25Q64_WaitBusy(void)
{
uint32_t Timeout=100000; //为了防止卡死
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
//直到busy不为1
while((MySPI_SwapByte(0xFF) & 0X01) == 0X01)
{
Timeout--;
if(Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
//页编程写入,注意页编程写入一页的范围
void W25Q64_PageProgram(uint32_t Address,uint8_t *DataArray,uint16_t count)
{
//没有24位,通过数组可以传多个字节。所以用32位,写入数据的数量范围0-256,所以用uint16不用uint8
W25Q64_WiteEnable();
uint16_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8); //高两位会丢弃
MySPI_SwapByte(Address);
for(i=0;i < count ;i++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_SectorErase(uint32_t Address)
{
//写使能仅对之后跟随的一条时序有效,结束之后会失能,所以每个函数加入这个就不用再写失能
W25Q64_WiteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
void W25Q64_ReadData(uint32_t Address,uint8_t *DataArray,uint32_t count) //改为32位
{
uint32_t i;
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for(i=0;i < count ;i++)
{
//调用交换读取之后,内部指针自动自增
DataArray[i]=MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "LED.H"
#include "Key.h"
#include "OLED.H"
#include "W25Q64.H"
uint8_t MID;
uint16_t DID;
uint8_t ArrayWrite[]={0x55,0x66,0x77,0x88};
uint8_t ArrayRead[4];
int main(void)
{
OLED_Init();
W25Q64_Init();
OLED_ShowString(1,1,"MID: DID:");
OLED_ShowString(2,1,"W:");
OLED_ShowString(3,1,"R:");
W25Q64_ReadID(&MID,&DID);
OLED_ShowHexNum(1,5,MID,2);
OLED_ShowHexNum(1,12,DID,4);
//只擦除不写入,可以验证flash擦除之后变为ff
//不擦除直接改写,可以测试不能由0到1,只能1到0
//写之前先擦除。xxx000-xxffff
W25Q64_SectorErase(0x000000);
//页地址范围xxxx00-xxxxff
W25Q64_PageProgram(0X000000,ArrayWrite,4);
W25Q64_ReadData(0X000000,ArrayRead,4);
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)
{
}
}
W25Q64_PageProgram(0X0000ff,ArrayWrite,4);
W25Q64_ReadData(0X000000,ArrayRead,4);
因为读可以跨页,此时读到的结果为 55 FF FF FF
改为这句W25Q64_ReadData(0X000000,ArrayRead,4);结果如下。说明不能跨页存储,超过范围会从页开始存储。
只需要修改底层MySPI文件,将软件实现改为硬件实现,之后基于通信层的业务代码不用改变
转移到移位寄存器后会自动生成波形,硬件自动生成不需要像软件那样置高低电平
#include "stm32f10x.h" // Device header
void MySPI_W_CS(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA,GPIO_Pin_4,(BitAction)BitValue);
}
void MySPI_Init(void)
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE);
GPIO_InitTypeDef GPIO_Init_Structure;
//硬件外设控制的输出MOSI,SCK;用复用推挽输出
GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_5 | GPIO_Pin_7;
GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Init_Structure);
//软件控制的输出信号ss,通用推挽输出
GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_Out_PP;
GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_4;
GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Init_Structure);
//MISO
GPIO_Init_Structure.GPIO_Mode=GPIO_Mode_IPU;
GPIO_Init_Structure.GPIO_Pin=GPIO_Pin_6;
GPIO_Init_Structure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_Init_Structure);
SPI_InitTypeDef SPI_InitStrcture;
SPI_InitStrcture.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_128;
//配置模式
SPI_InitStrcture.SPI_CPHA=SPI_CPHA_1Edge; //第一个边沿采样
SPI_InitStrcture.SPI_CPOL=SPI_CPOL_Low;
SPI_InitStrcture.SPI_CRCPolynomial=7;
SPI_InitStrcture.SPI_DataSize=SPI_DataSize_8b;
SPI_InitStrcture.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_InitStrcture.SPI_FirstBit=SPI_FirstBit_MSB; //高位先行
SPI_InitStrcture.SPI_Mode=SPI_Mode_Master; //主机模式
SPI_InitStrcture.SPI_NSS=SPI_NSS_Soft;
SPI_Init(SPI1,&SPI_InitStrcture);
//不要忘记开使能
SPI_Cmd(SPI1,ENABLE);
//默认不选择从机
MySPI_W_CS(1);
}
//三个基本时序:起始,交换数据,终止
void MySPI_Start(void)
{
MySPI_W_CS(0);
}
void MySPI_Stop(void)
{
MySPI_W_CS(1);
}
//四步
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_TXE) != SET);
SPI_I2S_SendData(SPI1,ByteSend);
//发送接收同步,当接收完成标志着发送也完成
while(SPI_I2S_GetFlagStatus(SPI1,SPI_I2S_FLAG_RXNE) != SET);
return SPI_I2S_ReceiveData(SPI1);
}
TXE RXNE标志位不用手动清除,中断标志位进中断后必须手动清除