4.SPI协议

5 篇文章 0 订阅

SPI总线介绍

SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。

SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。

在这里插入图片描述

SPI总线结构

SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。

在这里插入图片描述

  1. MOSI:主器件数据输出,从器件数据输入;
  2. MISO:主器件数据输入,从器件数据输出;
  3. SCLK :时钟信号,由主器件产生;
  4. /SS:从器件使能信号,由主器件控制(片选)。

SPI总线协议

在这里插入图片描述

起始信号:NSS信号线由高变低,是SPI通讯的起始信号;

结束信号:NSS信号由低变高,是SPI通讯的停止信号;

数据传输:SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线进行数据同步。MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。SPI每次数据传输可以 8 位或 16 位为单位,每次传输的单位数不受限制。

在这里插入图片描述

主从设备间数据交换逻辑示意

主机和从机都包含一个串行移位寄存器,主机通过向它的SPI串行寄存器写入一个字节发起一次传输。寄存器通过MOSI信号线将字节传送给从机,从机也将自己的移位寄存器中的内容通过MISO信号线返回给主机。这样两个移位寄存器中的内容就被交换了。从机的写操作和读操作时同步完成的,因此SPI成为一个很有效的协议。

SPI通信模式

SPI有四种通信模式,根据CPOL及CPHA的不同状态设置的。

在这里插入图片描述

时钟极性CPOL : 设置时钟空闲时的电平

当CPOL = 0 ,SCK引脚在空闲状态保持低电平;

当CPOL = 1 ,SCK引脚在空闲状态保持高电平。

时钟相位CPHA **:**设置数据采样时的时钟沿

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

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

在这里插入图片描述

在这里插入图片描述

STM32F4x SPI测试

SPI_Flash W25X16简介

W25X16有8192个可编程页,每页256字节。用“页编程指令”每次就可以编程256个字节。用扇区擦除指令每次可以擦除16页,用块擦除指令每次可以擦除256页,用整片擦除指令即可以擦除整个芯片。W25X16有512个可擦除扇区或32个可擦除块。

W25X16硬件连线

在这里插入图片描述

CS: 片选引脚,低电平有效,连接到STM32-PH2管脚

SO: 连接到STM32-PB4管脚(MISO)

SI: 连接到STM32-PB5管脚(MOSI)

CLK: 连接到STM32-PA5管脚(CLK)

WP: 写保护管脚,低电平有效,有效时禁止写入数据。接电源未使用

HOLD: HOLD 引脚可用于暂停通讯,该引脚为低电平时,通讯暂停,未使用

W25X16控制指令:

在这里插入图片描述

读制造商/设备ID(90)

该指令通常在调试程序的时候用到,判断SPI通信是否正常。该指令通过主器件拉低/CS片选使能器件开始传输,首先通过DI线传输“90H”指令,接着传输000000H的24位地址(A23-A0),之后从器件会通过DO线返回制造商ID(EFH)和设备ID。

(注:SPI为数据交换通信,主器件在发送“90H”指令时也会接收到一个字节FFH,但此数据为无效数据)

在这里插入图片描述

写使能命令(06H)

在向 FLASH 芯片存储矩阵写入数据前,首先要使能写操作,通过“Write Enable”命令即可写使能。

在这里插入图片描述

扇区擦除(20H)

​ 由于 FLASH 存储器的特性决定了它只能把原来为“1”的数据位改写成“0”,而原来为“0”的数据位不能直接改写为“1”。所以这里涉及到数据“擦除”的概念。

​ 在写入前,必须要对目标存储矩阵进行擦除操作,把矩阵中的数据位擦除为“1”,在数据写入的时候,如果要存储数据“1”,那就不修改存储矩阵 ,在要存储数据“0”时,才更改该位。

在这里插入图片描述

读状态寄存器(05H)

FLASH 芯片向内部存储矩阵写入数据需要消耗一定的时间,并不是在总线通讯结束的一瞬间完成的,所以在写操作后需要确认 FLASH 芯片“空闲”。我们只需要读取状态寄存器SRP的S0即可(当这个位为“1”时,表明 FLASH芯片处于忙碌状态,它可能正在对内部的存储矩阵进行“擦除”或“数据写入”的操作)。

在这里插入图片描述

读数据(03H)

​ 读数据指令可从存储器依次一个或多个数据字节,该指令通过主器件拉低/CS电平使能设备开始传输,然后传输“03H”指令,接着通过DI管脚传输24位地址,从器件接到地址后,寻址存储器中的数据通过DO引脚输出。每传输一个字节地址自动递增,所以只要时钟继续传输,可以不断读取存储器中的数据。

在这里插入图片描述

写数据——页编程(02H)

页编程指令可以在已擦除的存储单元中写入256个字节。该指令先拉低/CS引脚电平,接着传输“02H”指令和24位地址。后面接着传输至少一个数据字节,最多256字节。

注: 当数据写到一个新的扇区的时候,需要重新发起一个页编程信号才能继续写入数据。

在这里插入图片描述

w25x16.h

#ifndef __W25X16_H
#define __W25X16_H

#include "stm32f4xx_hal.h"



#define W25X_ManufactDeviceID  0x90  		/* Read identification */
#define sFLASH_CMD_WREN        0x06			/* Write enable instruction */
#define sFLASH_CMD_RDSR        0x05			/* Read Status Register instruction  */
#define sFLASH_CMD_SE          0x20			/* Sector Erase instruction */
#define sFLASH_CMD_WRITE       0x02  		/* Write to Memory instruction */
#define sFLASH_CMD_READ        0x03			/* Read from Memory instruction */	

#define sFLASH_DUMMY_BYTE      0x00
#define sFLASH_BUSY_FLAG        0x01
#define sFLASH_SPI_PAGESIZE       0x100

/*选中芯片,拉低片选*/
#define sFLASH_CS_LOW()     HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_RESET)
/*释放芯片,拉高片选*/
#define sFLASH_CS_HIGH()      HAL_GPIO_WritePin(GPIOH,GPIO_PIN_2,GPIO_PIN_SET)



uint8_t sFLASH_SendByte(uint8_t byte);
uint16_t sFLASH_ReadID(void);
void sFLASH_EraseSector(uint32_t SectorAddr);
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite);
void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead);




#endif

w25x16.c

#include "w25x16.h"
#include <stdio.h>

extern SPI_HandleTypeDef hspi1;

/*读写一个字节函数*/
uint8_t sFLASH_SendByte(uint8_t byte)
{
	uint8_t TX_DATA = byte;
	uint8_t RX_DATA = 0;
	
	HAL_SPI_TransmitReceive(&hspi1, &TX_DATA ,&RX_DATA , 1, 1000);
	
	return RX_DATA;
}

/*等待擦除或者写数据完成*/
void sFLASH_WaitForEnd(void)
{
	uint8_t sr_value = 0;
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_RDSR);
	
	do{
		
		sr_value = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
		
	}while( sr_value & sFLASH_BUSY_FLAG);

	
	sFLASH_CS_HIGH();
}

void sFLASH_WriteEnable(void)
{
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_WREN);
	
	sFLASH_CS_HIGH();
}


uint16_t sFLASH_ReadID(void)
{
	uint16_t FLASH_ID;
	uint8_t temp0,temp1;
	
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(W25X_ManufactDeviceID);
	
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	
	temp0 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	temp1 = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
	
	sFLASH_CS_HIGH();
	
	FLASH_ID = (temp0 << 8) | temp1;

	return FLASH_ID;
}

void sFLASH_EraseSector(uint32_t SectorAddr)
{
	sFLASH_WriteEnable();  //开启写使能
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_SE);
	sFLASH_SendByte( (SectorAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (SectorAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (SectorAddr>>0) & 0xff);      //传送底8位
	 
	sFLASH_CS_HIGH();
	
	/*等待擦除完成*/
	sFLASH_WaitForEnd();
	
}

void sFLASH_ReadBuffer(uint8_t* pBuffer, uint32_t ReadAddr, uint32_t NumByteToRead)
{
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_READ);
	sFLASH_SendByte( (ReadAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (ReadAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (ReadAddr>>0) & 0xff);      //传送底8位
	
	while(NumByteToRead--)
	{
		* pBuffer = sFLASH_SendByte(sFLASH_DUMMY_BYTE);
		pBuffer++;
	}

	sFLASH_CS_HIGH();
}

void sFLASH_WritePage(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
	//一次性只能写256字节
	if(NumByteToWrite > sFLASH_SPI_PAGESIZE )
	{
		NumByteToWrite = sFLASH_SPI_PAGESIZE;
		
		printf("写数据量太大,超过一页的大小\r\n");
	}
	
	sFLASH_WriteEnable();  //开启写使能
	
	sFLASH_CS_LOW();
	
	sFLASH_SendByte(sFLASH_CMD_WRITE);
	sFLASH_SendByte( (WriteAddr>>16) & 0xff);   //传送高8位
	sFLASH_SendByte( (WriteAddr>>8) & 0xff);     //传送中8位
	sFLASH_SendByte( (WriteAddr>>0) & 0xff);      //传送底8位
	
	while(NumByteToWrite--)
	{
		sFLASH_SendByte(* pBuffer);
		pBuffer++;
	}
	
	sFLASH_CS_HIGH();
	
	/*等待擦除完成*/
	sFLASH_WaitForEnd();
}

//分页处理数据
void sFLASH_WriteBuffer(uint8_t* pBuffer, uint32_t WriteAddr, uint32_t NumByteToWrite)
{
	uint16_t NumOfPage, NumOfBytes, count, offset;
	
	offset = WriteAddr % sFLASH_SPI_PAGESIZE;
	count = sFLASH_SPI_PAGESIZE - offset;//这一页处理剩下的没写的数据
	
	/*处理页不对齐的情况*/
	if(offset && (NumByteToWrite > count ))
	{
		sFLASH_WritePage(pBuffer,WriteAddr,count);
		
		NumByteToWrite -= count;
		
		pBuffer += count;
		WriteAddr += count;
	}
	//处理完成后,才是从新的页开始写
	
	NumOfPage = NumByteToWrite / sFLASH_SPI_PAGESIZE;
	NumOfBytes = NumByteToWrite % sFLASH_SPI_PAGESIZE;
	
	if(NumOfPage)
	{
			while(NumOfPage--)
			{
				sFLASH_WritePage(pBuffer,WriteAddr,sFLASH_SPI_PAGESIZE);
				pBuffer += sFLASH_SPI_PAGESIZE;
				WriteAddr += sFLASH_SPI_PAGESIZE;
			}
	}
	
	if(NumOfBytes)
	{
		sFLASH_WritePage(pBuffer,WriteAddr,NumOfBytes);
	}
}

main.c

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "w25x16.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
#include<stdio.h>

uint8_t RD_Buffer[5000] = {0};
uint8_t WR_Buffer[5000] = "SPI FLASH WRITE TEST\n";
/* USER CODE END PTD */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
	uint16_t FLASH_ID = 0;
	uint32_t i;
	
  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	
	printf("this is spi flash test\n");
	
	//读取设备厂商ID
	
	FLASH_ID = sFLASH_ReadID();
	
	printf("FLASH_ID = %x\n",FLASH_ID);//ef14 设备厂商ID

	/*测试擦除*/
	sFLASH_EraseSector(4096*0);
	sFLASH_EraseSector(4096*1);
//	sFLASH_ReadBuffer(RD_Buffer,0,4096);
//	printf("读数据开始\n");
//	for(i=0; i<4096; i++)
//	{
//		printf("%x ",RD_Buffer[i]);
//	}
//	printf("读数据结束\n");
//	
	
//	/*测试写操作1*/
//	sFLASH_WritePage(WR_Buffer,0, 20);
//	sFLASH_ReadBuffer(RD_Buffer,0,20);
//	printf("READ DATA: %s\n",RD_Buffer);
	
	/*测试写操作2*/
	for(i=0; i<4096; i++)
	{
		WR_Buffer[i] = 0x55;
	}
	sFLASH_WriteBuffer(WR_Buffer,4090, 1000);
	sFLASH_ReadBuffer(RD_Buffer,4090,1000);
	for(i=0; i<1000; i++)
	{
		printf("%x ",RD_Buffer[i]);
	}
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}


  • 14
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
w25x16 SPI FLASH读写 串口监测输出STC8A8K单片机KEIL工程文件源码 FLASH w25x16 2M外部flash测试 W25X16芯片,就是16Mbit 一byte等于8bit 也就是2M字节的存储空间。 256bytes为一页 4Kbytes为一个Sector (扇区) 16个扇区为1个Block (块) W25X16 容量为2M字节,共有32个Block,512个Sector 而且W25X16最小擦除量是一个扇区 即4k字节空间 W25X16擦写周期多达 10W次,具有 20年的数据保存期限, 支持电压为 2.7~3.6V ,最大SPI 时钟可以到80Mhz。 程序上是将一个字符串存到了flash地址100开始的位置,然后去读取存入的数据到数组中,在将读到的 数组数据其显示出来 整个过程由串口检测 主频为11.0592MHz 串口波特率为9600 */ #include "stc8.h" //STC15头文件 #include "def.h" //宏定义 常用函数 #include "delay.h" //延时函数 #include "spi.h" #include "flash.h" #include "uart.h" u8 scan[]={"STC8 FLASH test"}; //测试字符串 u8 buffer[19]; //接收数组 void main() { SP=0X80; //调整堆栈指向 手册286页 详解 Init_SPI(); //SPI初始化 UartInit(); //串口初始化 if(SPI_Flash_ReadID()==0xef14) UartSendStr("外部FLASH初始化成功!\r\n"); else { UartSendStr("外部FLASH初始化失败!\r\n"); while(1); } SPI_Flash_Erase_Sector(0); //擦除地址为0扇区 4k字节 SPI_Flash_Write_NoCheck(scan,100,15); //在地址100位置写入字符串 SPI_Flash_Read(buffer,100,16); //在地址100位置处读取字符串并存入buffer数组中 UartSendStr("地址100数据:\r\n"); UartSend(buffer,15); while(1); }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值