接线图
功能函数
//写SS函数
void My_W_SS(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)BitValue);
}//写SCK函数
void My_W_SCK(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)BitValue);
}//写MOSI函数
void My_W_MOSI(uint8_t BitValue)
{GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)BitValue);
}//读MISO函数
uint8_t My_R_MISO(void)
{return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
代码配置
1.开启时钟
//开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);//开启GPIO A族的时钟
2.配置GPIO
引脚配置,将输出引脚配置为推挽输出,输入引脚配置为上拉输入,这里接线图,DO对应从机输出,对应主机PA6就是输入,其他三个引脚配置为推挽输出。
GPIO_InitTypeDef GPIO_InitStructure;//定义GPIO结构体变量 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);
3.配置电平
My_W_SS(1);//置高电平,默认不选择从机
My_W_SCK(0);//默认选择低电平,使用SPI模式0
配置SPI时序基本单元
起始条件与终止条件
起始条件:SS从高电平切换到低电平
终止条件:SS从低电平切换到高电平
//起始条件
void My_SPI_Start(void)
{//置低电平My_W_SS(0);
}//终止条件
void My_SPI_Stop(void)
{//置高电平My_W_SS(1);
}
交换一个字节
方法一
使用掩码,以此提取数据的每一位
//交换一个字节
uint8_t My_SPI_SwapByte(uint8_t ByteSend)
{uint8_t i, ByteReceive = 0x00;for(i = 0; i < 8; i++){//写MOSIMy_W_MOSI(ByteSend & (0x80 >> i));//SCK上升沿 置高电平My_W_SCK(1);//读MISOif(My_R_MISO() == 1){ByteReceive |= (0x80 >> i);}//SCK产生下降沿 置低电平My_W_SCK(0);}return ByteReceive;}
方法二
用移位数据本身来进行操作,好处是效率高,但是ByteSend数据在移位的过程中改变了,for循环执行完,原始传入的参数就没有了
//交换一个字节
uint8_t My_SPI_SwapByte2(uint8_t ByteSend)
{uint8_t i;for(i = 0; i < 8; i++){//写MOSIMy_W_MOSI(ByteSend & 0x80 );ByteSend <<= 1;//SCK上升沿 置高电平My_W_SCK(1);//读MISOif(My_R_MISO() == 1){ByteSend |= 0x01;}//SCK产生下降沿 置低电平My_W_SCK(0);}return ByteSend;}
SPI配置完成,接下来建立W25Q64的驱动层
W25Q64配置
#include "stm32f10x.h"
#include "MySPI.h"void W25Q64_Init(void)
{MySPI_Init();
}
接下来实现业务代码,拼接完整时序
宏定义指令集
根据手册指令表将指令宏定义出来
//宏定义指令集
#define W25Q64_WRITE_ENABLE 0x06
#define W25Q64_WRITE_DISABLE 0x04
#define W25Q64_READ_STATUS_REGISTER_1 0x05
#define W25Q64_READ_STATUS_REGISTER_2 0x35
#define W25Q64_WRITE_STATUS_REGISTER 0x01
#define W25Q64_PAGE_PROGRAM 0x02
#define W25Q64_QUAD_PAGE_PROGRAM 0x32
#define W25Q64_BLOCK_ERASE_64KB 0xD8
#define W25Q64_BLOCK_ERASE_32KB 0x52
#define W25Q64_SECTOR_ERASE_4KB 0x20
#define W25Q64_CHIP_ERASE 0xC7
#define W25Q64_ERASE_SUSPEND 0x75
#define W25Q64_ERASE_RESUME 0x7A
#define W25Q64_POWER_DOWN 0xB9
#define W25Q64_HIGH_PERFORMANCE_MODE 0xA3
#define W25Q64_CONTINUOUS_READ_MODE_RESET 0xFF
#define W25Q64_RELEASE_POWER_DOWN_HPM_DEVICE_ID 0xAB
#define W25Q64_MANUFACTURER_DEVICE_ID 0x90
#define W25Q64_READ_UNIQUE_ID 0x4B
#define W25Q64_JEDEC_ID 0x9F
#define W25Q64_READ_DATA 0x03
#define W25Q64_FAST_READ 0x0B
#define W25Q64_FAST_READ_DUAL_OUTPUT 0x3B
#define W25Q64_FAST_READ_DUAL_IO 0xBB
#define W25Q64_FAST_READ_QUAD_OUTPUT 0x6B
#define W25Q64_FAST_READ_QUAD_IO 0xEB
#define W25Q64_OCTAL_WORD_READ_QUAD_IO 0xE3#define W25Q64_DUMMY_BYTE 0xFF
实现获取ID号的时序
*MID输出8位的厂商ID
*DID输出16位的设备ID
//获取ID号的时序
void W25Q64_ReadID(uint8_t* MID, uint16_t* DID)
{//开始传输My_SPI_Start();//交换发送一个字节 9FMy_SPI_SwapByte(W25Q64_JEDEC_ID);//给从机一个东西,目的是将对方的数据置换过来*MID = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);//返回设备ID的高8位*DID = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);*DID <<= 8; //把第一次读取的数据运到DID的高8位去//返回设备ID的低8位*DID |= My_SPI_SwapByte(W25Q64_DUMMY_BYTE);//结束时序My_SPI_Stop();
}
写使能
//写使能
void W25Q64_WriteEnable(void)
{My_SPI_Start();My_SPI_SwapByte(W25Q64_WRITE_ENABLE);My_SPI_Stop();}
读状态寄存器1
判断芯片忙不忙
//读状态寄存器1
//判断芯片是否处于忙状态
void W25Q64_WaitBusy(void)
{uint32_t Timeout;My_SPI_Start();My_SPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);//等待BUSYwhile ((My_SPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01) == 0x01){Timeout--;if(Timeout == 0){break;}}My_SPI_Stop();
}
页编程函数
根据Flash注意事项
写入操作前必须先写使能,写入操作后等待忙状态,这里的页编程与扇形擦除进行了写入操作,需要注意事项。
//页编程
void W25Q64_PageProgram(uint32_t Address, uint8_t* DataArray, uint16_t Count)
{uint16_t i;W25Q64_WriteEnable();//写使能My_SPI_Start();My_SPI_SwapByte(W25Q64_PAGE_PROGRAM);//指定24位地址My_SPI_SwapByte(Address >> 16);My_SPI_SwapByte(Address >> 8);My_SPI_SwapByte(Address);for(i = 0; i < Count; i++){My_SPI_SwapByte(DataArray[i]);}My_SPI_Stop();W25Q64_WaitBusy();//等待BUSY(事后等待)
}
扇区擦除函数
//扇区擦除函数
void W25Q64_SectorErase(uint32_t Address)
{W25Q64_WriteEnable();//写使能My_SPI_Start();My_SPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);//指定24位地址My_SPI_SwapByte(Address >> 16);My_SPI_SwapByte(Address >> 8);My_SPI_SwapByte(Address);My_SPI_Stop();W25Q64_WaitBusy();//等待BUSY(事后等待)}
读取数据
//读取数据
void W25Q64_ReadData(uint32_t Address, uint8_t* DataArray, uint32_t Count)
{uint32_t i;My_SPI_Start();My_SPI_SwapByte(W25Q64_READ_DATA);//指定24位地址My_SPI_SwapByte(Address >> 16);My_SPI_SwapByte(Address >> 8);My_SPI_SwapByte(Address);for(i = 0; i < Count; i++){DataArray[i] = My_SPI_SwapByte(W25Q64_DUMMY_BYTE);}My_SPI_Stop();}
主函数
#include "W25Q64.h"
int main(void)
{uint8_t MID;uint16_t DID;uint8_t ArraryWrite[] = {0x01, 0x02, 0x03, 0x04};uint8_t ArraryRead[4];OLED_Init();W25Q64_Init();OLED_ShowString(1, 1, "MID:");OLED_ShowString(1, 8, "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);W25Q64_SectorErase(0x000000);W25Q64_PageProgram(0x000000, ArraryWrite, 4);W25Q64_ReadData(0x000000, ArraryRead, 4);OLED_ShowHexNum(2, 3, ArraryWrite[0], 2);OLED_ShowHexNum(2, 6, ArraryWrite[1], 2);OLED_ShowHexNum(2, 9, ArraryWrite[2], 2);OLED_ShowHexNum(2, 12,ArraryWrite[3], 2);OLED_ShowHexNum(3, 3, ArraryRead[0], 2);OLED_ShowHexNum(3, 6, ArraryRead[1], 2);OLED_ShowHexNum(3, 9, ArraryRead[2], 2);OLED_ShowHexNum(3, 12,ArraryRead[3], 2);while(1){}}