STM32-基本知识梳理7-SPI通讯及flashW25Q64BV控制

写在前面,本章节是为下一篇:FATFS文件系统移植,做准备;

(快写好,文章突然少了一半????????,气的头晕)

一、SPI通讯基本简述

1,是高速全双工、同步的通信总线,广泛地使用在ADC、LCD等设备与MCU间,要求通讯速率较高的场合;

2,硬件引脚结构

①SCK-时钟同步,由主机发送时钟信号,从机接受,并按照要求执行相应动作

②MOSI:master  output  slave  input,主机输出,从机接受

③MISO:-------------------------------------,主机接受,从机输出

④SS:片选(从机收到SS低电平,人为被选中,与主机进行通讯),SPI模块中,一般不选用SPI模块的自带引脚,而是使用GPIO中的引脚的推挽模式进行输出;

(这样可以”无限“扩容 SPI的从机,而不受SPI模块SS引脚的数量限制)

3,SPI通讯的基本过程

①NSS片选(即上述的CS引脚),低电平表示选中模块,开始通讯;高电平表示停止通讯;

②触发位置,表示在这里可以进行数据位bit的变化;(一般是SCK的上升沿或者下降沿)

③采样位置,主机或者从机对MOSI或者MISO,进行数据的采集(一般是SCK的上升沿或者下降沿,具体由对应模式来决定);

4,CPOL/CPHA及通讯模式

上述在触发的采样的位置描述很模糊,原因:

实际到底在上升沿触发还是下降沿触发,上升沿采样还是下降沿采样,是由CPOL/CPHA决定

时钟极性CPOL:是指SPI通讯设备处于空闲状态时,SCK的电平状态;

CPOL=1,表示SCK在NSS片选位高电平时刻(未选中),SCK保持高电平状态;

CPOL=0,表示SCK在NSS片选位高电平时刻(未选中),SCK保持低电平状态;

 时钟相位CPHA:是指数据的采样的时刻,即MOSI 、MISO的被主机或者采集采样的时间,是上升沿还是下降沿;

CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样;

CPHA=1时,MOSI或MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样;

补充:奇数边沿、偶数边沿,就会关联到CPOL了,如果CPOL=0,那么奇数边沿就是上升沿

SPI四种模式

至此,SPI的基本介绍完毕;


二、STM32-SPI模块介绍

1,基本介绍

1-STM32的SPI外设可用作通讯的主机及从机;在多模块通讯的时刻 ,STM32会作为从机模式

2-支持最高的SCK时钟频率为fpclk/2 (STM32F10x型号的芯片默认fpclk1为72MHz,fpclk2为36MHz);这个主要和SPI所在时钟线决定

SPI1最高

3-完全支持SPI协议的4种模式;一般选用模式0和模式3,主要看从机芯片手册中,适用那种模式。主机和从机的协议模式要保持一致;

4-数据帧长度可设置为8位或16位;一般选用8位传递

5-可设置数据MSB先行或LSB先行,需要和从机保持一致,不然数据会异常;

6-支持双线全双工

三、STM32的SPI框图解析

 ①外部引脚:

②波特率发生器:主要根据时钟线给入的值,进行分频处理,分频值和控制模块的BR寄存器数值关联。一般采样2分频、4分频、8分频等等

③数据发送、接受模块:主要由三个寄存器组成,接受缓冲区、移位寄存器、发送缓冲区

在发送数据时刻,先将数据放到发送缓冲区,再由移位寄存器一位一位的发送出去;

需要注意的是:

1-数据发送,发送缓冲区非空,则表示数据发给到移位寄存器了,但是不表示数据发送完毕;

一般在接受寄存器非空时刻,认为数据发送完毕(具体看后续代码,这边记住就好);

2-数据接受,SPI中无法只接受,只要在发送数据时刻,才会启动SCK时钟,所有在数据接受过程中,发送部分要一致发空信号,比如0x00

④控制模块

1- 配置分频

2- 配置LSB\MSB先行

3- 8位、16位数据传递

四、STM32-gpio引脚配置

这边参考STM32芯片手册推荐方式配置

五、SPI模式结构体配置说明

至此STM32- SPI介绍完毕


六、FALSH模块介绍W25Q64BV

这边相等于对于芯片手册 ,进行一次说明

1,引脚说明

CS:片选

DO:  连接SPI的MISO

DI:  连接SPI的MOSI

clk:连接时钟clk

其余引脚按需配置

2,内存空间说明

 空间由128个块组成,每一个块大小64kb,每一个块被分成16个扇区,其中每个扇区大小4kb

3,可写入标志位

对于内存写入的操作,无论是eeprom还是flash,都写入的时间都会相对较长,在写入数据或者读取数据前,要确认当前flash模式是否处于busy状态

4,出厂标号

出厂标号,是flash的内部写入值,一般是用于通讯识别spi和flash正常进行通讯;

 5,W25Q64BV的各种指令

对于flash的各种操作都是通过SPI发送指令,从而达到和flash进行指令交互

指令的发送和数据接受发送协议,是控制flash交互最重要的部分

①写使能

//写使能
void write_enable(void)
{
	chip_start();
	spi_sendbit(0x06);
	chip_stop();
	printf("写使能成功!\n");
}

②读寄存器状态

//等待flash
void wait_nobusy(void)
{
	uint8_t state;
	chip_start();
	spi_sendbit(0x05);
	do{
	 
		state=spi_sendbit(0x00);
	
	}while((state&0x01)==1);
	
	chip_stop();
}

③读数据寄存器,最大一次读取4096个字节

//读取
void read_buffer(uint32_t addr,uint8_t* readdata,uint16_t readnum)
{
    //等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x03);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);

	while(readnum)
	{
		* readdata=spi_sendbit(0x00);
		readdata++;
		readnum--;
	}
	printf("buffer读取成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

④写数据,最大写入256一次,写入前必须进行擦除

//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
	//写使能开启
	write_enable();
	//等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x20);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);
	printf("buffer擦除成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

//写入前必须,先擦除
void write_buffer(uint32_t addr,uint8_t* writedata,uint16_t writenum)
{
	//写使能开启
	write_enable();
	//等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x02);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);
	while(writenum)
	{
		spi_sendbit(* writedata);
		writedata++;
		writenum--;
	}
	printf("buffer写入成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

⑤擦除数据

为什么写入数据前要擦除扇区:flash中的数据,只能从1变成0,而不能从0到1 ,所以每次写入数据时刻,要先将扇区擦除掉,再写入

每次擦除的

//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
	//写使能开启
	write_enable();
	//等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x20);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);
	printf("buffer擦除成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

⑥读取设备号信息 

uint32_t read_flash_id(void)
{
	uint32_t flashid=0;
	//开始
	chip_start();
	spi_sendbit(0x9f);
	
	flashid =spi_sendbit(0x00);
	flashid<<=8;
	flashid|=spi_sendbit(0x00);
	flashid<<=8;
	flashid|=spi_sendbit(0x00);
	
	chip_stop();
	return flashid;

}

至此,flashW25Q64BV介绍


下面正式进入代码模块

一、代码逻辑梳理

1,初始化时钟

2,初始化GPIO

3,初始化SPI1

4,启动spi1

5,配置启动和停止模块

6,配置发送 8bit数据并接受8bit数据传递(基本模块)

7,配置读falsh设备id指令

8,写使能

9,等待flash非busy

10,擦除使能

11,写入数据

12,读取数据

1,初始化时钟

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //gpio时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1,ENABLE); //spi模块时钟

2,初始化GPIO

	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_Out_PP; //PIN4  CS片选 普通推挽输出
	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; //PIN5  CLK时钟 复用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_5;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //PIN6  MISO 复用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init( GPIOA, &GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //PIN7  MOSI 复用推挽输出
	GPIO_InitStruct.GPIO_Pin=GPIO_Pin_7;
	GPIO_Init( GPIOA, &GPIO_InitStruct);

3,初始化SPI1

 SPI_InitTypeDef SPI_InitStruct;
	 SPI_InitStruct.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2; //二分频
	 SPI_InitStruct.SPI_CPHA=SPI_CPHA_2Edge;
	 SPI_InitStruct.SPI_CPOL=SPI_CPOL_High ;//采用模式3,空闲高电平,偶数边沿采样
	 
	 SPI_InitStruct.SPI_CRCPolynomial=0;//不使用CRC校验
	 SPI_InitStruct.SPI_DataSize=SPI_DataSize_8b ;
	 SPI_InitStruct.SPI_Direction=SPI_Direction_2Lines_FullDuplex;//全线全双工
	 SPI_InitStruct.SPI_FirstBit=SPI_FirstBit_MSB;//高位先行
	 
	 SPI_InitStruct.SPI_Mode=SPI_Mode_Master; //主机模式
	 SPI_InitStruct.SPI_NSS=SPI_NSS_Soft;//片选使用软件配置
	 SPI_Init( SPI1, &SPI_InitStruct);

4,启动spi1

	SPI_Cmd( SPI1,ENABLE);

5,配置启动和停止模块

void chip_start(void)
{
	GPIO_ResetBits(GPIOA,GPIO_Pin_4);

}

void chip_stop(void)
{
	GPIO_SetBits(GPIOA,GPIO_Pin_4);

}

6,配置发送 8bit数据并接受8bit数据传递(基本模块)

uint8_t spi_sendbit(uint8_t byte)
{
	//等待发送寄存器为空
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)==RESET);
	//发送数据
	SPI_I2S_SendData(SPI1, byte);
	//等待接受寄存器非空,表示数据发送完毕
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)==RESET);
	
	//返回接受数据
	printf("发送完成\n");
	return SPI_I2S_ReceiveData( SPI1);

}

7,配置读falsh设备id指令

uint32_t read_flash_id(void)
{
	uint32_t flashid=0;
	//开始
	chip_start();
	spi_sendbit(0x9f);
	
	flashid =spi_sendbit(0x00);
	flashid<<=8;
	flashid|=spi_sendbit(0x00);
	flashid<<=8;
	flashid|=spi_sendbit(0x00);
	
	chip_stop();
	return flashid;

}

8,写使能

//写使能
void write_enable(void)
{
	chip_start();
	spi_sendbit(0x06);
	chip_stop();
	printf("写使能成功!\n");
}

9,等待flash非busy

//等待flash
void wait_nobusy(void)
{
	uint8_t state;
	chip_start();
	spi_sendbit(0x05);
	do{
	 
		state=spi_sendbit(0x00);
	
	}while((state&0x01)==1);
	
	chip_stop();
}

10,擦除使能

//扇区擦除,擦除前要加上写使能
void clear_sector(uint32_t addr)
{
	//写使能开启
	write_enable();
	//等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x20);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);
	printf("buffer擦除成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

11,写入数据,最大256

//写入前必须,先擦除
void write_buffer(uint32_t addr,uint8_t* writedata,uint16_t writenum)
{
	//写使能开启
	write_enable();
	//等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x02);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);
	while(writenum)
	{
		spi_sendbit(* writedata);
		writedata++;
		writenum--;
	}
	printf("buffer写入成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

12,读取数据,最大4096

//读取
void read_buffer(uint32_t addr,uint8_t* readdata,uint16_t readnum)
{
    //等待flash no busy
	wait_nobusy();
	//启动
	chip_start();
	spi_sendbit(0x03);
	spi_sendbit((addr>>16)&0xff);
    spi_sendbit((addr>>8)&0xff);
	spi_sendbit((addr)&0xff);

	while(readnum)
	{
		* readdata=spi_sendbit(0x00);
		readdata++;
		readnum--;
	}
	printf("buffer读取成功!\n");
	chip_stop();
	//等待flash no busy
	wait_nobusy();
}

在主函数中进行简单配置

#include "stm32f10x.h"                  // Device header
#include "LED.H"
#include "BSP_USART.H"
#include "BSP_SPI.H"

void delay(uint32_t time)
{
	while(time--)
	{
	
	}

}
uint8_t writebuf[30];
uint8_t readbuf[30];
uint32_t id;
uint8_t i=0;
int main(void)
{
	bsp_usart_config();
	spi_flash_config();

	for(i=0;i<30;i++)
    {
		writebuf[i]=i;
	}
	
	id=read_flash_id();
	printf("id=%x\n",id);
	clear_sector(0);
	write_buffer(0,writebuf,30);
	read_buffer(0,readbuf,30);
	
	
	for(i=0;i<30;i++)
    {
		printf("0x%x ",readbuf[i]);
		if((i%10==0)&&(i!=0))
			printf("\n");
	}
	while(1)
	{
		
	}
	
}

最后补充一下,上面的写入是最大写入256个字节,如果想要无限字节(在内存允许下)的写入,则需要将原本的数据,进行分块处理,这边直接借用了野火的代码

/**
  * @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
  * @param	pBuffer,要写入数据的指针
  * @param  WriteAddr,写入地址
  * @param  NumByteToWrite,写入数据长度
  * @retval 无
  */
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
  u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
	
	/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/
  Addr = WriteAddr % SPI_FLASH_PageSize;
	
	/*差count个数据值,刚好可以对齐到页地址*/
  count = SPI_FLASH_PageSize - Addr;
	/*计算出要写多少整数页*/
  NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
	/*mod运算求余,计算出剩余不满一页的字节数*/
  NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
	
	/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */
  if (Addr == 0)
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0) 
    {
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    { 
			/*先把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			/*若有多余的不满一页的数据,把它写完*/
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
    }
  }
	/* 若地址与 SPI_FLASH_PageSize 不对齐  */
  else 
  {
		/* NumByteToWrite < SPI_FLASH_PageSize */
    if (NumOfPage == 0)
    {
			/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/
      if (NumOfSingle > count) 
      {
        temp = NumOfSingle - count;
				/*先写满当前页*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
				
        WriteAddr +=  count;
        pBuffer += count;
				/*再写剩余的数据*/
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);
      }
      else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
      }
    }
    else /* NumByteToWrite > SPI_FLASH_PageSize */
    {
			/*地址不对齐多出的count分开处理,不加入这个运算*/
      NumByteToWrite -= count;
      NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
      SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
			
			/* 接下来就重复地址对齐的情况 */
      WriteAddr +=  count;
      pBuffer += count;
			/*把整数页都写了*/
      while (NumOfPage--)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
        WriteAddr +=  SPI_FLASH_PageSize;
        pBuffer += SPI_FLASH_PageSize;
      }
			/*若有多余的不满一页的数据,把它写完*/
      if (NumOfSingle != 0)
      {
        SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
      }
    }
  }
}

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值