附工程百度网盘链接
链接:https://pan.baidu.com/s/1nCgNb5OyGpABAL657-gX0A?pwd=6666
提取码:6666
介绍:摩托罗拉开发的一种通用数据总线,
四根通讯线SCK(串行时钟总线),MOSI(主机输出从机输入),MISO(主机输入从机输出),SS(从机选择)而且是同步全双工,支持总线挂载多个设备
有的名字可能是DI,DO,CS,CLK:DO就是设备输入对应接MOSI,DO对应就是设备输出对应接MISI
SPI传输更快,SPI每多挂载一根设备就多一根线,主机要和哪个设备通讯就把谁的SS线拉低电平
补充:当从机的SS线没有被拉低的时候那么改从机的MISO为高组态,相当于断开
SPI起始条件:;SS从高电平切换到低电平
SPI的终止条件:SS从低电平切换到高电平
SPI的数据传输模式
CPOL(时钟极性)和CPHA(时钟相位)决定了模式
模式0: CPOL=0(空闲状态时SCL为低电平) COHA=0(SCK第一个边沿移入数据,第二个边沿移出数据),它和模式4的区别在于SCL取反
模式1: CPOL=0(空闲状态时SCL为低电平) COHA=1(SCK第一个第一个边沿移出数据,第二个边沿移入数据)它和模式3的区别在于SCL取反
W25Q64介绍
地址是24位的 (00 00 00 h- 7F FF FF)共计:80 00 00 (HEX) /1024/1024=8M
BLock(块):8M的空间被切割成128块,每块64kb
扇区:每块64kb又每切割成16个扇区,每扇区4kb(16*4=64kb)
页:一扇区是4KB,划分成16份每份256字节这就是页,而且擦除数据也只能按照扇区或者块来擦除
下图的00FF00H-00FFFFH刚好256字节
000000h-0000ff之间也是256字节
所以我们也可以算出W25Q64总共多少页
8M/256kb=32,768页
倒回去算也可以
16(页)*16(扇区)*128(块)=0x80000
SPI Command & Control Logic:SPI命令与控制逻辑
Status Register:状态寄存器
Write Control Logic:写控制逻辑
我们发送的24位地址例如 00 00 FF :高位1个字节对应的就是块地址(Page Address Latch / Counter), 低位2个字节表示一页内的字节地址(Byte Address Latch / Counter)
这里解释一下为什么高位1个字节对应的就是页地址 低位2个字节表示一页内的字节地址?
先来看块的地址每个块地址都是高两位在变动而第四位是没有变化的
例如:第0块 000000h-00FFFF;
第1块 010000h-01FFFF;
第31块 01F000h-1FFFFF;
再来看看扇区地址变化的都是低四位
并且连续写入的数据量不能超过256字节
代码部分软件模拟SPI
void 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_W_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);
MySPI_W_SCK(0);
}
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,Byte=0x00;
for(i=0;i<8;i++)
{
MySPI_W_MOSI(ByteSend & (0x80>>i));
MySPI_W_SCK(1);
if(MySPI_W_MISO()==1){Byte|=(0x80>>i);}
MySPI_W_SCK(0);
}
return Byte;
}
//模式03
uint8_t MySPI_swapByte3(uint8_t ByteSend)
{
uint8_t i,Byte=0x00;
for(i=0;i<8;i++)
{
MySPI_W_MOSI(ByteSend & (0x80>>i));
MySPI_W_SCK(0);
if(MySPI_W_MISO()==1){Byte|=(0x80>>i);}
MySPI_W_SCK(1);
}
return Byte;
}
这里解释一下为什么要用模式0和模式3?因为手册这样写的
void W25Q64_ReadID(uint8_t *MID,uint16_t *DID)
{
MySPI_Start();
MySPI_swapByte3(0X9F);
*MID=MySPI_swapByte3(0xff);
*DID=MySPI_swapByte3(0xff);
*DID <<= 8;
*DID|=MySPI_swapByte3(0xff);
MySPI_Stop();
}
//扇区擦除
void W25Q64_SectorErase(uint32_t Address)
{
W25Q64_WriteEnable();
MySPI_Start();
MySPI_swapByte3(W25Q64_SECTOR_ERASE_4KB);
MySPI_swapByte3(Address >> 16);
MySPI_swapByte3(Address >> 8);
MySPI_swapByte3(Address);
MySPI_Stop();
W25Q64_WaitBusy();
}
什么叫页,什么叫块,什么叫扇区上边已经解释很清楚了
000000H-0000FF这就是一页,一页刚好256个字节,由于不能跨页写入所以才有了手册里面说的连续写入不能超过256个字节,
什么叫不能跨页写入?例如:我从0000FF这个地址开始往W25Q64里面放4个字节的数据,这样是不行的,放不进去的。只能重新指定页地址也就是起始地址改成第二页的起始地址重新放这样才能放进去
//页写入
void W25Q64_PageProgram(uint32_t Address, uint8_t *DataArray, uint16_t Count)
{
uint16_t i;
W25Q64_WriteEnable();
MySPI_Start();
MySPI_swapByte3(W25Q64_PAGE_PROGRAM);
MySPI_swapByte3(Address>>16);
MySPI_swapByte3(Address>>8);
MySPI_swapByte3(Address);
for ( i = 0; i < Count; i ++)
{
MySPI_swapByte3(DataArray[i]);
}
MySPI_Stop();
W25Q64_WaitBusy();
}
//读取数据
void W25Q64_ReadData(uint32_t Address, uint8_t *DataArray, uint32_t Count)
{
uint32_t i;
MySPI_Start();
MySPI_swapByte3(W25Q64_READ_DATA);
MySPI_swapByte3(Address >> 16);
MySPI_swapByte3(Address >> 8);
MySPI_swapByte3(Address);
for (i = 0; i < Count; i ++)
{
DataArray[i] = MySPI_swapByte3(W25Q64_DUMMY_BYTE);
}
MySPI_Stop();
}
接下来这个是一个实验的函数
往指定块的指定扇区里面的指定页存数据
//页写入
/*
Block:块地址 0-127
Sector:扇区地址 0-15
Page:页地址 0-15
DataArray:要写入内容
Count:写入的数量0-15
*/
void W25Q64_AnyProgram(uint8_t Block, uint8_t Sector, uint16_t Page,uint8_t *DataArray, uint16_t Count)
{
uint8_t i;
//指定块的起始地址(64kb)
uint32_t BlockAddressStart=0x000000;
//扇区的起始地址
uint16_t SectorAddress=0x000000;
for(i=0;i
调用示例
W25Q64_AnyProgram(0,1,1,ArrayWrite, 3);
//往第0块中的第1扇区里面的第1页,存入ArrayWrite数组里面的数据,存3个
W25Q64_ReadData(0x2000, ArrayRead, 4);
实际效果显示没什么问题
这里说一下为什么读取的时候地址是0X2000,
因为第0块中的第1扇区地址起始是0X001000 而扇区里面的第1页地址是0x002000
硬件SPI后边更新