SPI通讯介绍 以及读写W25Q64(块,扇区,页的区别)

附工程百度网盘链接

链接: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

首先初始化要用到的GPIO

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发送

//模式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; 
}

模式3发送

//模式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?因为手册这样写的 

读取厂商ID

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<Block;i++)
	{
	BlockAddressStart+=0x10000;
	}
	for(i=0;i<Sector;i++)
	{
	SectorAddress=BlockAddressStart+0x1000;
	}
	//页
	uint16_t PageAddress=SectorAddress;
	for(i=0;i<Page;i++)
	{
	PageAddress=SectorAddress+0x1000;
	}	
	
	W25Q64_SectorErase(PageAddress);
	W25Q64_PageProgram(PageAddress,DataArray,Count);
}

调用示例

	W25Q64_AnyProgram(0,1,1,ArrayWrite, 3);
//往第0块中的第1扇区里面的第1页,存入ArrayWrite数组里面的数据,存3个
	W25Q64_ReadData(0x2000, ArrayRead, 4);

实际效果显示没什么问题

 这里说一下为什么读取的时候地址是0X2000,

因为第0块中的第1扇区地址起始是0X001000     而扇区里面的第1页地址是0x002000

 硬件SPI后边更新

  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
SPI(Serial Peripheral Interface)是一种串行通信协议,可以用于与外设进行数据交互。W25Q64是一款64Mb的串行Flash存储器,支持SPI接口。 以下是使用C语言进行SPI读写W25Q64的示例代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/spi/spidev.h> #define SPI_DEVICE "/dev/spidev0.0" // SPI设备节点路径 #define BUFFER_SIZE 256 // 缓冲区大小 int main() { int fd, ret; uint8_t tx_buffer[BUFFER_SIZE], rx_buffer[BUFFER_SIZE]; struct spi_ioc_transfer tr; // 打开SPI设备 fd = open(SPI_DEVICE, O_RDWR); if (fd < 0) { perror("Failed to open SPI device"); exit(1); } // 初始化SPI传输结构体 memset(&tr, 0, sizeof(tr)); tr.tx_buf = (unsigned long)tx_buffer; tr.rx_buf = (unsigned long)rx_buffer; tr.len = BUFFER_SIZE; tr.speed_hz = 10000000; tr.bits_per_word = 8; // 发送写命令 tx_buffer[0] = 0x06; // 写使能 ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 0) { perror("Failed to send write enable command"); exit(1); } tr.len = 4; tx_buffer[0] = 0x02; // 写指令 tx_buffer[1] = 0x00; // 地址高位 tx_buffer[2] = 0x00; // 地址中位 tx_buffer[3] = 0x00; // 地址低位 memcpy(tx_buffer + 4, "Hello", 5); // 写入数据 // 发送写数据命令 ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 0) { perror("Failed to send write command"); exit(1); } // 发送读命令 tr.len = 4; tx_buffer[0] = 0x03; // 读指令 tx_buffer[1] = 0x00; // 地址高位 tx_buffer[2] = 0x00; // 地址中位 tx_buffer[3] = 0x00; // 地址低位 ret = ioctl(fd, SPI_IOC_MESSAGE(1), &tr); if (ret < 0) { perror("Failed to send read command"); exit(1); } printf("Data read from flash memory: %s\n", rx_buffer + 4); // 关闭SPI设备 close(fd); return 0; } ``` 以上代码演示了如何使用SPI接口读写W25Q64芯片。其中,首先通过ioctl函数设置SPI传输参数和指令,然后通过ioctl函数发送指令,并通过缓冲区读取或写入数据。 需要注意的是,通过SPI接口读写W25Q64芯片需要按照其通信协议进行操作,具体指令和数据格式可参考W25Q64的数据手册。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值