SPI总线介绍
SPI接口是Motorola 首先提出的全双工三线同步串行外围接口,采用主从模式(Master Slave)架构;支持多slave模式应用,一般仅支持单Master。时钟由Master控制,在时钟移位脉冲下,数据按位传输,高位在前,低位在后(MSB first);SPI接口有2根单向数据线,为全双工通信,目前应用中的数据速率可达几Mbps的水平。
SPI总线被广泛地使用在FLASH、ADC、LCD等设备与MCU间,要求通讯速率较高的场合。
SPI总线结构
SPI接口共有4根信号线,分别是:设备选择线、时钟线、串行输出数据线、串行输入数据线。
- MOSI:主器件数据输出,从器件数据输入;
- MISO:主器件数据输入,从器件数据输出;
- SCLK :时钟信号,由主器件产生;
- /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 */
}