SPI介绍
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI协议
SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制
FLASH介绍
FLSAH 存储器又称闪存,它与 EEPROM 都是掉电后数据不丢失的存储器,但 FLASH存储器容量普遍大于 EEPROM,现在基本取代了它的地位。我们生活中常用的 U盘、SD卡、SSD 固态硬盘以及我们 STM32 芯片内部用于存储程序的设备,都是 FLASH 类型的存储器。在存储控制上,最主要的区别是FLASH 芯片只能一大片一大片地擦写,而EEPROM可以单个字节擦写。
W25Q64
w25q64有64MB内存,一页256个字节4096字节也就是(4KB)为一个扇区16个扇区为一块(65536字节)一共128块(8388608字节)
接线
MOIS->PB15(SPI2)
MISO->PB14(SPI2)
SCK->PB13(SPI2)
CS->PG2
配置
w25q64.c
#include "W25Q64.h"
/**********************
spi协议发送1个字节函数
uint8_t BYTE :你需要发送的字节数据
*********************/
uint8_t W25QX_SendByte(uint8_t BYTE)
{
uint8_t SNED_BYTE = BYTE;
uint8_t READ_BYTE = 0;
HAL_SPI_TransmitReceive(&hspi2, &SNED_BYTE, &READ_BYTE, 1, 1000);
return READ_BYTE;
}
/**********************
读id函数
返回芯片的ID
*********************/
uint16_t W25QX_Read_ID(void)
{
uint16_t W25QX_ID;
uint8_t temp0, temp1;
CS_LOW;
W25QX_SendByte(W25QX_Manufacturer_Device_ID);
W25QX_SendByte(kong_DATA);
W25QX_SendByte(kong_DATA);
W25QX_SendByte(kong_DATA);
temp0 = W25QX_SendByte(0xff); // 第一次返回厂家id
temp1 = W25QX_SendByte(0xff); // 第二次返回唯一id
CS_HIGH;
W25QX_ID = (temp0 << 8) | temp1;
return W25QX_ID;
}
/**********************
验证id函数
uint16_t ID : 你需要验证的芯片id
成功返回1,否则返回0
*********************/
uint8_t Verification_ID(uint16_t ID)
{
if (W25QX_Read_ID() == w25Q64)
{
return 1;
}
else
{
return 0;
}
}
/**********************
等待flash空闲函数 擦除写入时需
要等待flash完成调用此函数
*********************/
void Await_Status_Register(void)
{
CS_LOW;
W25QX_SendByte(Read_Status_Register_CMD);
while (W25QX_SendByte(kong_DATA) & 0x01)
{
};
CS_HIGH;
}
/**********************
写使能函数
使能falsh的写入功能
*********************/
void W25QX_Write_Enable(void)
{
CS_LOW;
W25QX_SendByte(Write_Enable_CMD);
CS_HIGH;
}
/**********************
失能函数
失能falsh的写入功能
*********************/
void W25QX_Write_Disable(void)
{
CS_LOW;
W25QX_SendByte(Write_Disable_CMD);
CS_HIGH;
}
/**********************
扇区擦除函数
uint32_t addr :要擦除扇区的地址4096的倍数
*********************/
void W25QX_Sector_Erase(uint32_t addr)
{
W25QX_Write_Enable();
CS_LOW;
W25QX_SendByte(Sector_Erase_CMD);
W25QX_SendByte((addr >> 16) & 0xff); // 一次传输一个字节右移16位取高八位
W25QX_SendByte((addr >> 8) & 0xff); // 一次传输一个字节右移8位取中八位
W25QX_SendByte(addr & 0xff); // 取低八位
CS_HIGH;
Await_Status_Register(); // 等待擦除完成
}
/**********************
块擦除函数
uint32_t addr :要擦除块区的地址65536的倍数
*********************/
void W25QX_Block_Erase(uint32_t addr)
{
W25QX_Write_Enable();
CS_LOW;
W25QX_SendByte(Block_Erase_CMD);
W25QX_SendByte((addr >> 16) & 0xff); // 一次传输一个字节右移16位取高八位
W25QX_SendByte((addr >> 8) & 0xff); // 一次传输一个字节右移8位取中八位
W25QX_SendByte(addr & 0xff); // 取低八位
CS_HIGH;
Await_Status_Register(); // 等待擦除完成
}
/*
全部擦除函数
等待时间比较长,别不信
你用下就知道了
*/
void W25QX_Erase_Chip(void)
{
W25QX_Write_Enable(); // 开启写使能
CS_LOW;
W25QX_SendByte(Chip_Erase_CMD);
CS_HIGH;
Await_Status_Register(); // 等待擦除完成
}
/*
进入省电模式
*/
void W25QX_Power_Down(void)
{
CS_LOW;
W25QX_SendByte(Power_Down_CMD );
CS_HIGH;
HAL_Delay(1);
}
/*
快来唤醒它吧嘿嘿嘿
*/
void W25QX_Wake_Up(void)
{
CS_LOW;
W25QX_SendByte(Wake_Up_CMD);
CS_HIGH;
HAL_Delay(1);
}
/**********************
读数据函数
uint8_t * Rdata_Buf:要存放读出flash的数据包地址
uint32_t Write_addr : 开始读flash地址
uint32_t Write_size : 要读出的字节数
*********************/
void W25QX_Read_Data(uint8_t *Rdata_Buf, uint32_t Read_addr, uint32_t Read_size)
{
CS_LOW;
W25QX_SendByte(Read_Data_CMD);
W25QX_SendByte((Read_addr >> 16) & 0xff); // 一次传输一个字节右移16位取高八位
W25QX_SendByte((Read_addr >> 8) & 0xff); // 一次传输一个字节右移8位取中八位
W25QX_SendByte(Read_addr & 0xff); // 取低八位
// flash内部会自动增加地址只管一直读就完事了
while (Read_size--)
{
*Rdata_Buf = W25QX_SendByte(kong_DATA);
Rdata_Buf++;
}
// 一直读到我们将片选拉高为止
CS_HIGH;
}
/**********************
页写入函数
uint8_t * Wdata_Buf :要写入flash的数据包地址
uint32_t Write_addr : 开始写入flash的地址
uint32_t Write_size :要写入的字节数
*********************/
void W25QX_Write_Data(uint8_t *Wdata_Buf, uint32_t Write_addr, uint32_t Write_size)
{
if (Write_size > Page_Size)
{
Write_size = Page_Size;
}
W25QX_Write_Enable(); // 开启写使能
CS_LOW;
W25QX_SendByte(Write_Data_CMD);
W25QX_SendByte((Write_addr >> 16) & 0xff); // 一次传输一个字节右移16位取高八位
W25QX_SendByte((Write_addr >> 8) & 0xff); // 一次传输一个字节右移8位取中八位
W25QX_SendByte(Write_addr & 0xff); // 取低八位
while (Write_size--)
{
W25QX_SendByte(*Wdata_Buf);
Wdata_Buf++;
}
CS_HIGH;
Await_Status_Register(); // 等待写完成
}
/********************************
升级版函数可以写入任意地址
uint8_t * Write_data :要写入flash的数据包地址
uint32_t Write_addr : 开始写入flash的地址
uint32_t Write_size :要写入的字节数
*********************************/
void W25QX_Write_Data_ProMAX(uint8_t *Write_data, uint32_t Write_addr, uint32_t Write_size)
{
uint32_t PageNum, BYTES, Misaligned_value, Residual_value;
Misaligned_value = Write_addr % PAGESIZE;
Residual_value = PAGESIZE - Misaligned_value;
/***
未页对其
Misaligned_value 偏差
计算有没有对齐,对齐就是0,没对齐就有偏差数据
列如果Write_addr填入的0 or 256,一页的倍数取余数就是0,
如果填入257 就余1,说明这1页Write_addr的位置在地址1这里
Residual_value 剩余
就可以用页数减去Write_addr的位置值,就得到了这页还剩余的字节数,
我们就可以先把这1页剩余的字节数给写满
***/
if ((Misaligned_value != 0) && (Write_size > Residual_value)) // 如果余数不为0和写数据字节数大于这一页剩余字节数数说明没有对齐
{
W25QX_Write_Data(Write_data, Write_addr, Residual_value); // 首先将这一页剩余的字节数写满
Write_size -= Residual_value; // 要写入的字节数减去这一页写满的字节数得到剩余要写的字节数
Write_data += Residual_value; // 写入的数据包地址加上这一页写满的字节数得到下一次数据包需要写入的地址
Write_addr += Residual_value; // 开始地址加上这一页写满的字节数得到下一次开始写的地址
}
/*页对齐*/
PageNum = Write_size / PAGESIZE; // 计算出有多少页
BYTES = Write_size % PAGESIZE; // 计算还余下多少字节
if (PageNum >= 1) // 说明有1页及以上数据
{
while (PageNum--) // 页数不为0一直按页写入
{
W25QX_Write_Data(Write_data, Write_addr, PAGESIZE); // 写入一个页的字节数
Write_addr += PAGESIZE; // 地址加上一页的字节数等于下一次要写入的地址
Write_data += PAGESIZE; // 数据包地址加上一页的字节数等于下一次数据包开始的地址
}
}
if (BYTES) // 说明有还有不满足一页条件的数据
{
W25QX_Write_Data(Write_data, Write_addr, BYTES); // 写入剩余的字节数
}
}
w25q64.h
#ifndef __w25q64_H__
#define __w25q64_H__
#include "stm32f1xx_hal.h"
#include "spi.h"
#include "SYSTEM.h"
#define w25Q64 0X0b16 //ID号
#define Page_Size 256
#define Sector_Size 4096
/**
本程序用于w25q64,其他内存flash通用
有些地方稍做修改即可
w25q64有64MB内存
一页256个字节
4096字节也就是(4KB)为一个扇区
16个扇区为一块(65536字节)
一共128块(8388608字节)
**/
/**
指令兼容w25qx全系列
**/
#define W25QX_Manufacturer_Device_ID 0X90 //读取厂商id
#define Write_Enable_CMD 0X06 //开启写使能
#define Write_Disable_CMD 0X04 //关闭写使能
#define Read_Status_Register_CMD 0X05 //读状态寄存器
#define Sector_Erase_CMD 0X20 //扇区擦除4kB
#define Block_Erase_CMD 0XD8 //块擦除64kB
#define Chip_Erase_CMD 0xC7 //全部擦除
#define Write_Data_CMD 0X02 //写命令
#define Read_Data_CMD 0X03 //读命令
#define Power_Down_CMD 0xB9 //省电模式
#define Wake_Up_CMD 0XAB //唤醒模式
#define kong_DATA 0X00 //空字节
#define CS_LOW PGout(13)=0
#define CS_HIGH PGout(13)=1
uint8_t W25QX_SendByte(uint8_t BYTE);//发送一个字节
uint16_t W25QX_Read_ID(void);//读取id
uint8_t Verification_ID(uint16_t ID);//验证id成功返回1,否则返回0
void W25QX_Sector_Erase(uint32_t addr);//擦除扇区
void W25QX_Block_Erase(uint32_t addr);
void W25QX_Erase_Chip(void);//擦除整块芯片
void W25QX_Read_Data(uint8_t * Rdata_Buf,uint32_t Read_addr,uint32_t Read_size);//读数据
void W25QX_Write_Data(uint8_t * Wdata_Buf,uint32_t Write_addr,uint32_t Write_size);//页写数据
void W25QX_Write_Data_ProMAX(uint8_t * Write_data,uint32_t Write_addr,uint32_t Write_size);//随意写
void W25QX_Power_Down(void);
void W25QX_Wake_Up(void) ;
#endif