相对I2C的优点缺点:1,传输速度快,可达80MHz;2,设计简单,学习容易;3,硬件资源消耗多,容易造成资源浪费
CPHA决定了是那个SCK开始采样
模式2和模式3类似与这两个模式,只不过SCK的高低电平翻转了而已
Dual SPI: 双重SPI模式,在一个SCK电平变化下,一次性交换两个数据
Quad SPI:四重SPI模式,同双重SPI模式
对于引脚旁括号的内容:即双重SPI模式或者四重SPI模式下的多位数据传输的通道,当为双重SPI模式时,DO(I01)和DI(IO0)就是一次性传输的两位数据,IO2和IO3同理。
一整个存储空间,首先先被划分为若干块,对应每一块又被划分为若干扇区,对于整个空间,又会被划分为很多页,每页256个字节
Page地址锁存器:用于指定我们要操作的页
Byte地址锁存器:用于指定操作我们指定的页中指定的字节
我们发送的24位地址(3字节地址)前两位是Page地址,会发送到Page地址锁存器中,后一位是字节地址,会发送到Byte地址锁存器中。
以上两个寄存器都有计数器,所以他们的地址指针是可以在读写之后可以自动加1的,实现从指定地址开始,连续读写多个字节的目的了。
写入的数据会先在RAM缓存区(Column Decode)中存储,在时序结束后,芯片再把缓存区中的数据复制到对应的Flash中,进行永久保存
因为SPI的写入频率非常高,而数据是要放进Flash中永久存储的,这个过程比较慢,所以写入的数据先放在页缓存区中存着,缓存区是RAM,所以速度非常快,可以跟上SPI总线的速度,但是这个缓存区也是有内存限制的(256Byte),所以写入的一个时序,连续写入的数据量不能超过256字节。
当我们发送好数据后,芯片才慢慢地把页缓存区的数据传入到Flash中,这会占用一定的时间,写入时序结束后,芯片就会进入“Buzy”的时间内,这时就会置标志位到status寄存器中,在这段时间内,芯片将不会响应新的读写时序。
写入操作时:
读取操作时:
Buzy如之前所说;
写使能:在每写入一个数据后,状态寄存器会自动配置为写失能,代表我们每写入一个字节之气那都需要配置为写使能,每一个写使能只能保证后续的一条写指令
ID
页编程(前三个字节指定地址,后面一个写入数据,如果继续写入数据,指定的地址会自动加1,但是要注意范围为页)
擦除指令(标注的是最小的扇区擦除)
读取数据
1,开启时钟,配置GPIO口(软件SPI任选引脚)
void MySPI_Init(void)
{
//引脚初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
MySPI_W_SS(1);
MySPI_W_SCK(0);
}
2,与软件I2C类似,使用函数封装一下置每个引脚高低电平的操作(模拟SPI)
//用于模拟每个端口的操作
void MySPI_W_SS(uint8_t Value)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_4, (BitAction)Value);
}
void MySPI_W_SCK(uint8_t Value)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_5, (BitAction)Value);
}
void MySPI_W_MOSI(uint8_t Value)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_7, (BitAction)Value);
}
uint8_t MySPI_R_MISO(void)
{
GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6);
}
同时需要在初始化函数中添加
MySPI_W_SS(1);
MySPI_W_SCK(0);
3,根据时序来编写各个操作的函数
void MySPI_Start(void){
MySPI_W_SS(0);
}
void MySPI_Stop(void){
MySPI_W_SS(1);
}
//掩码方式(优点,可以保持ByteSend不变)
uint8_t MySPI_WriteReadByte(uint8_t ByteSend)
{
uint8_t i, ByteReceive=0x00;
for (i = 0; i < 8; i ++)
{
MySPI_W_SCK(1);
MySPI_W_MOSI(ByteSend & (0x80 >> i));
if (MySPI_R_MISO() == 1){ByteReceive |= (0x80 >> i);}
MySPI_W_SCK(0);
}
return ByteReceive;
}
//移位版(实现思路与SPI传输数据相同)
uint8_t MySPI_WriteReadByte1(uint8_t ByteSend)
{
uint8_t i;
for (i = 0; i < 8; i ++)
{
MySPI_W_SCK(1);
MySPI_W_MOSI(ByteSend & 0x80);
ByteSend <<= 1;
if (MySPI_R_MISO() == 1){ ByteSend |= 0x01;}
MySPI_W_SCK(0);
}
return ByteSend;
}
4,编写W25Q64的相关函数
void W25Q64_Init(void)
{
MySPI_Init();
}
void W25Q64_GetID(uint8_t *MID, uint16_t *DID)
{
MySPI_Start();
//指令集中获取设备ID号的指令
MySPI_WriteReadByte(W25Q64_JEDEC_ID);
//第一个字节为厂商ID
*MID = MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
//第二个字节为设备ID高8位,第三个字节位设备ID低8位
*DID = MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_WriteReadByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
#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();
//指令集中获取设备ID号的指令
MySPI_SwapByte(W25Q64_JEDEC_ID);
//第一个字节为厂商ID
*MID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
//第二个字节为设备ID高8位,第三个字节位设备ID低8位
*DID = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
*DID <<= 8;
*DID |= MySPI_SwapByte(W25Q64_DUMMY_BYTE);
MySPI_Stop();
}
void W25Q64_WriteEnable(void)
{
MySPI_Start();
MySPI_SwapByte(W25Q64_WRITE_ENABLE);
MySPI_Stop();
}
void W25Q64_WaitBusy(void)
{
MySPI_Start();
//读取状态寄存器比较特殊,写入以下值后,是连续读出状态寄存器的值
uint32_t Timeout=100000;
MySPI_SwapByte(W25Q64_READ_STATUS_REGISTER_1);
while ((MySPI_SwapByte(W25Q64_DUMMY_BYTE) & 0x01 )== 0x01)
{
Timeout--;//防止卡死
if (Timeout == 0)
{
break;
}
}
MySPI_Stop();
}
//注意一次性只能发送一页(256个字节)的数据,Count最大值也只能是256
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
//事前等待(更高效,但是在每个操作函数的开头都需要加)
W25Q64_WaitBusy();
//写使能
W25Q64_WriteEnable();
MySPI_Start();
//指令集中的PageProgram
MySPI_SwapByte(W25Q64_PAGE_PROGRAM);
//传输地址
//因为地址是24位的,所以需要把地址分成8位一份发给从机
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
//发送数据
for (uint16_t i = 0; i < Count; i ++)
{
MySPI_SwapByte(DataArray[i]);
}
MySPI_Stop();
}
//擦除一个扇形区
void W25Q64_SectorErase(uint32_t Address)
{
//事前等待(更高效,但是在每个操作函数的开头都需要加)
W25Q64_WaitBusy();
//写使能
W25Q64_WriteEnable();
MySPI_Start();
MySPI_SwapByte(W25Q64_SECTOR_ERASE_4KB);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
MySPI_Stop();
}
void W25Q64_ReadData(uint32_t Address, uint8_t *Array, uint32_t Count)
{
//事前等待(更高效,但是在每个操作函数的开头都需要加)
W25Q64_WaitBusy();
MySPI_Start();
MySPI_SwapByte(W25Q64_READ_DATA);
MySPI_SwapByte(Address >> 16);
MySPI_SwapByte(Address >> 8);
MySPI_SwapByte(Address);
for (uint32_t i = 0; i < Count; i ++)
{
Array[i] = MySPI_SwapByte(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
主函数
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "W25Q64.h"
uint8_t MID;
uint16_t DID;
uint8_t Array_W[4] = {0x01, 0x02, 0x03, 0x04};
uint8_t Array_R[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);
W25Q64_SectorErase(0x000000);
W25Q64_PageProgram(0x000000, Array_W, 4);
W25Q64_ReadData(0x000000, Array_R, 4);
OLED_ShowHexNum(2,3,Array_W[0],2);
OLED_ShowHexNum(2,6,Array_W[1],2);
OLED_ShowHexNum(2,9,Array_W[2],2);
OLED_ShowHexNum(2,12,Array_W[3],2);
OLED_ShowHexNum(3,3,Array_R[0],2);
OLED_ShowHexNum(3,6,Array_R[1],2);
OLED_ShowHexNum(3,9,Array_R[2],2);
OLED_ShowHexNum(3,12,Array_W[3],2);
while(1)
{
}
}
非连续传输的等待空隙在频率比较高时没太大影响,但是一旦频率很低,这个影响就不能被忽略了,所以我们在传输比较高频率的信号时不能使用非连续传输,可以使用连续输出或者进一步采用DMA自动转运。
其他内容真得看手册吧啊!
不用介绍
void SPI_I2S_DeInit(SPI_TypeDef* SPIx);
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
void I2S_Init(SPI_TypeDef* SPIx, I2S_InitTypeDef* I2S_InitStruct);
void SPI_StructInit(SPI_InitTypeDef* SPI_InitStruct);
void I2S_StructInit(I2S_InitTypeDef* I2S_InitStruct);
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void I2S_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
void SPI_I2S_ITConfig(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT, FunctionalState NewState);
void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
//发送和接收数据
void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx);
标志位哥们
FlagStatus SPI_I2S_GetFlagStatus(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
void SPI_I2S_ClearFlag(SPI_TypeDef* SPIx, uint16_t SPI_I2S_FLAG);
ITStatus SPI_I2S_GetITStatus(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
void SPI_I2S_ClearITPendingBit(SPI_TypeDef* SPIx, uint8_t SPI_I2S_IT);
//引脚初始化
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_4;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
SPI_InitTypeDef SPI_InitStruct;
//主从
SPI_InitStruct.SPI_Mode = SPI_Mode_Master;
/*裁剪SPI引脚的,即选择:
单线半双工的接收模式Rx
单线半双工的发送模式Tx
双线全双工Full
双线只接收模式RxOnly*/
SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
//数据帧8位还是16位
SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;
//低位先行or高位先行
SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;
//SCK时钟的频率(即选择分频系数
SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
//SPI模式
//时钟极性-这里选择了默认为低电平
SPI_InitStruct.SPI_CPOL = SPI_CPOL_Low;
//1Edge即为0,2Enge即为1
SPI_InitStruct.SPI_CPHA = SPI_CPHA_1Edge;
//软件实现NSS
SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;
//不用了解
SPI_InitStruct.SPI_CRCPolynomial = 7;
SPI_Init(SPI1,&SPI_InitStruct);
SPI_Cmd(SPI1, ENABLE);
MySPI_W_SS(1);
uint8_t MySPI_SwapByte(uint8_t ByteSend)
{
//必须发送同时接收,两个过程是绑定进行的
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE));
SPI_I2S_SendData(SPI1, ByteSend);
//在发送的同时MISO还会移位进行接收,发送和接收是同步的
//接收移位完成了也就代表发送移位完成了
//接收完成时会置标志位RXNE,我们可以借此来判断是否发送完数据
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
//标志位不需手动清除
//读取数据
return SPI_I2S_ReceiveData(SPI1);
}