引言

实现SPI通讯,对FLASH进行读写。读取FLASH的ID信息,写入数据,并读取出来进行校验,通过串口打印写入与读取出来的数据,输出测试结果。

一、SPI总线

SPI通信的基础知识

SPI是串行外设接口(Serial Peripheral Interface),即串行外围设备接口,是一种高速全双工的通信总线,正是出于这种简单易用的特性,如今越来越多的芯片集成了这种通信协议,最大SPI速度可达到18MHz 。

通常SPI通过4个管脚与外部器件相连:

MISO:主设备输入/从设备输出管脚。

MOSI:主设备输出/从设备输入管脚。

SCK:串口时钟,作为主设备的输出,从设备的输入。

NSS:从设备选择。这是一个可选的管脚,用来选择主/从设备。它的功能是用来作为“片选管脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

时钟信号的相位和极性

SPI_CR寄存器的CPOL和CPHA位,能够组合成四种可能的时序关系,其中使用的最为广泛的是SPI0和SPI3方式。

SPI模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。如果CPOL=0,串行同步时钟的空闲状态为低电平;如果CPOL=1,串行同步时钟的空闲状态为高电平。时钟相位(CPHA)能够配置用于选择两种不同的传输协议之一进行数据传输。如果CPHA=0,在串行同步时钟的第一个跳变沿(上升或下降)数据被采样;如果CPHA=1,在串行同步时钟的第二个跳变沿(上升或下降)数据被采样。SPI主模块和与之通信的外设音时钟相位和极性应该一致。

这些时序体现了SPI_CR1寄存器的LSBFIRST被重置(置0)时的情况,即MSB模式。

SPI模式

CPOL

CPHA

空闲时SCK时钟

采样时刻

0

0

0

低电平

奇数边沿

1

0

1

低电平

偶数边沿

2

1

0

高电平

奇数边沿

3

1

1

高电平

偶数边沿

SPI主模式配置

在主配置时,串行时钟在SCK脚产生,学会使用寄存器,了解底层逻辑(用库函数开发也会更加明了),配置步骤如下:

1、通过SPI_CR1寄存器的BR[2:0]位定义串行时钟波特率。

SPI控制寄存器 1(SPI_CR1)

2、选择CPOL和CPHA位,定义数据传输和串行时钟间的相位关系。

3、设置DFF位来定义8或16位数据帧格式。

4、配置SPI_CR1寄存器的LSBFIRST位定义帧格式。

5、如果NSS引脚需要工作在输入模式,硬件模式中在整个数据帧传输期间应把NSS脚连接到高 电平;在软件模式中,需设置SPI_CR1寄存器的SSM和SSI位。

6、必须设置MSTR和SPE位(只当NSS脚被连到高电平,这些位才能保持置位)。在这个配置中,MOSI脚是数据输出,而MISO脚是数据输入。

举例:void SPI1_Init(void)
{    RCC->APB2ENR|=1<<2;          //PORTA时钟使能      RCC->APB2ENR|=1<<12;         //SPI1时钟使能 GPIOA->CRL&=0X000FFFFF;     //PA5/6/7清零GPIOA->CRL|=0XBBB00000;       //PA5/6/7复用GPIOA->ODR|=0X7<<5;         //PA5.6.7上拉   GPIOA->CRL|=0X000000300;    //PA2 SPI主设备时输出SPI1->CR1|=0<<10;           //全双工模式 SPI1->CR1|=1<<9;          //软件NSS管理SPI1->CR1|=1<<8;  SPI1->CR1|=1<<2;             //SPI主机SPI1->CR1|=0<<11;          //8bit数据格式  SPI1->CR1|=1<<1;          //空闲模式下SCK为1 CPOL=1SPI1->CR1|=1<<0;          //数据采样从第二个时间边沿开始,CPHA=1  SPI1->CR1|=2<<5;             //Fpclk1/8SPI1->CR1|=0<<7;            //先发送MSB   SPI1->CR1|=1<<6;           //SPI设备使能
}

数据发送过程

当一字节写进发送缓冲器时,发送过程开始。在发送第一个数据位时,数据字被并行地(通过内部总线)传入移位寄存器,而后串行地移出到MOSI脚上;MSB在先还是LSB在先,取决于SPI_CR1寄存器中的LSBFIRST位。如果设置SPI_CR2寄存器中的TXEIE位,将产生中断。

SPI控制寄存器 2(SPI_CR2)

数据接收过程

对于接收器来说,当发送完一帧数据的时候,“状态寄存器SR”中的“TXE标志位”会被置1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置1,表示传输完一帧,接收缓冲区非空;读SPI_DR寄存器时,将清除RXNE位,SPI设备返回接收到的数据。

SPI状态寄存器(SPI_SR)

代码:
//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
uint8_t SPI_FLASH_SendByte(uint8_ TxData)
{uint16_t  retry=0;while((SPI2->SR&1<<1)==0)       //等待发送区空 {retry++;if(retry>=0XFFFE)return 0;   //超时退出}SPI1->DR=TxData;             //发送一个byte retry=0;while((SPI2->SR&1<<0)==0)       //等待接收完一个byte  {retry++;if(retry>=0XFFFE)return 0; //超时退出}return SPI2->DR;                //返回收到的数据
}

二、FLASH控制

本文使用的FLASH与SPI总线的硬件连接如下图所示。

软件方面FLASH定义了很多的指令,确定通讯协议模块,通过协议收发命令、数据,进而驱动设备,因此首先需要了解芯片时序,通过熟悉底层逻辑从而编写驱动,也能为日后学习打下坚实的基础。下面仔细罗列为FLASH其中读取厂商ID的驱动实现步骤,其他驱动见文后代码清单。

FLASH指令集

读取Device ID时序图

通过结合指令中厂商ID所需指令和时序图,拉低片选,使能FLASH,利用用户函数SPI_FLASH_SendByte()来向FLASH发送第一个命令字节编码:“W25X_DeviceID”(自定义的宏:0xAB);根据指令表,发送完这个指令后,后面紧跟着三个字节的“Dummy byte”,可以自定义任意值,这里定义为“0xFF”,根据时序,在第5个字节,FLASH通过SO端口输出它的器件ID,我们调用函数SPI_FLASH_SendByte()接收返回的数据,并赋值给Temp变量。SPI_FLASH_ReadDeviceID()函数的返回值即为读取得的器件 ID。把片选信号拉高,结束通讯。这就完成了读FLASH 厂商ID。

代码:uint32_t SPI_FLASH_ReadDeviceID(void)
{uint32_t Temp=0; SPI_FLASH_CS_LOW(); SPI_FLASH_SendByte(W25X_DeviceID);       //发送读取指令SPI_FLASH_SendByte(Dummy_Byte);          //三个Dummy_ByteSPI_FLASH_SendByte(Dummy_Byte); SPI_FLASH_SendByte(Dummy_Byte); Temp = SPI_FLASH_SendByte(Dummy_Byte);   //接收返回值SPI_FLASH_CS_HIGH(); return Temp;
}

编译调试后获得厂商ID,与下表进行对比,确定芯片选择正确,W25Q16厂商ID为0x14。

制造厂商和设备ID

三、代码编写

main.c#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress//#define countof(a)    (sizeof(a) / sizeof(*(a)))
//#define  BufferSize (countof(Tx_Buffer)-1)
#define  BufferSize 255uint8_t Tx_Buffer[255]={0};
//uint8_t Tx_Buffer[] = "士不可以不弘毅,任重而道远\r\n";
uint8_t Rx_Buffer[BufferSize];int main()
{uint16_t DeviceID;uint16_t FlashID;int cnt=0;USART1_GPIO_Config(); USART1_Config();SPI_FLASH_Init();printf("\r\n 这是一个 2M 串行 flash(W25X16)实验 \r\n");/* 获得 Flash Device ID */DeviceID = SPI_FLASH_ReadDeviceID();        DelayMS(200);/* 获得 Flash ID */FlashID = SPI_FLASH_ReadID();printf("\r\n FlashID is 0x%X, Manufacturer Device ID is 0x%X\r\n", FlashID, DeviceID);/* 擦除Flash数据 */SPI_FLASH_BulkErase();SPI_FLASH_SectorErase(0);printf("\r\n擦除完毕\r\n");/* 将发送缓冲区的数据写到 flash 中 */ SPI_FLASH_Buffer_Write(Tx_Buffer, FLASH_WriteAddress, BufferSize);{ printf("\r\n 写入的数据为: \r\t");for(cnt=0;cnt<BufferSize;cnt++){Tx_Buffer [cnt]=cnt;printf(" 0x%02X   ", Tx_Buffer[cnt]);}}//printf("\r\n 写入的数据为:%d \r\t", Tx_Buffer[cnt]); /* 将写入的数据读出来放到接收缓冲区 */SPI_FLASH_Buffer_Read(Rx_Buffer, FLASH_ReadAddress, BufferSize);{printf("\r\n 读出的数据为: \r\t");for(cnt=0;cnt<BufferSize;cnt++){Rx_Buffer [cnt]=cnt;printf(" 0x%02X   ", Rx_Buffer[cnt]);}}//printf("\r\n 读出的数据为:%d \r\n", Rx_Buffer[cnt]);printf("\r\n 2M串行flash(W25Q16)测试成功!\n\r");SPI_Flash_PowerDown();while(1){} }     
spi.cuint32_t time_out = SPI_FLASH_WAIT_TIMEOUT;
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);void SPI_FLASH_Init(void)
{SPI_InitTypeDef SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;/* 使能SPI1 和 GPI时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);/* SCK */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);/* MISO */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;GPIO_Init(GPIOA, &GPIO_InitStructure);/* MOSI */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;GPIO_Init(GPIOA, &GPIO_InitStructure);/* CS */GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_Init(GPIOA, &GPIO_InitStructure);SPI_FLASH_CS_HIGH();/* SPI1配置 */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(SPI1, &SPI_InitStructure);SPI_Cmd(SPI1, ENABLE);
}//发送一个字节(这里原理同前文寄存器编写代码)uint8_t SPI_FLASH_SendByte(uint8_t byte)
{ uint8_t read_temp;    time_out = SPI_FLASH_WAIT_TIMEOUT;while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET) {                                                                                          if(time_out-- == 0)        //超时{return SPI_TIMEOUT_UserCallback(1);}}SPI_I2S_SendData(SPI1, byte); time_out = SPI_FLASH_WAIT_TIMEOUT;/* Wait to receive a byte */while (SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET) {if(time_out-- == 0){return SPI_TIMEOUT_UserCallback(2);}}   read_temp = (uint8_t)SPI_I2S_ReceiveData (SPI1);   return read_temp; }//接收一个字节
uint8_t SPI_FLASH_Receive_Byte(void)
{return SPI_FLASH_SendByte(0xFF);
}//出错时调用这个函数
static  uint16_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{printf("\r\nSPI 检测超时,错误代码:%d",errorCode);return 0;
}
flash.h
#ifndef __FLASH_H
#define __FLASH_H       #include "sys.h" #define W25Q16   0XEF14
#define FLASH_ID 0XEF14#define  SPI_FLASH_CS PAout(2)
#define SPI_FLASH_CS_HIGH()  GPIO_SetBits(GPIOA, GPIO_Pin_2)
#define SPI_FLASH_CS_LOW()    GPIO_ResetBits(GPIOA, GPIO_Pin_2)
#define SPI_FLASH_PageSize         256
#define SPI_FLASH_PerWritePageSize  256
#define  SPI_FLASH_WAIT_TIMEOUT  10000//指令表
#define W25X_WriteEnable        0x06
#define W25X_WriteDisable       0x04
#define W25X_ReadStatusReg      0x05
#define W25X_WriteStatusReg     0x01
#define W25X_ReadData           0x03
#define W25X_FastReadData       0x0B
#define W25X_FastReadDual       0x3B
#define W25X_PageProgram        0x02
#define W25X_BlockErase         0xD8
#define W25X_SectorErase        0x20
#define W25X_ChipErase          0xC7
#define W25X_PowerDown          0xB9
#define W25X_ReleasePowerDown   0xAB
#define W25X_DeviceID           0xAB
#define W25X_ManufactDeviceID   0x90
#define W25X_JedecDeviceID      0x9F #define Dummy_Byte 0xFFuint32_t SPI_FLASH_ReadDeviceID(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
uint8_t SPI_FLASH_Receive_Byte(void);
uint32_t SPI_FLASH_ReadID(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);
void SPI_FLASH_Page_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_Buffer_Read(uint8_t* pBuffer, uint8_t ReadAddr, uint8_t NumByteToRead);
void SPI_FLASH_Buffer_Write(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_Flash_PowerDown(void);#endif   /* __FLASH_H */
usart.c
void USART1_GPIO_Config()                                                /* GPIO配置 */
{       GPIO_InitTypeDef GPIO_InitStruct;                                    /* 结构体定义 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);               /* USART1时钟 */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);                /* GPIOA使能 */GPIO_InitStruct.GPIO_Pin=GPIO_Pin_9;                                 /* PA2引脚 ,USART的TX*/GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP;                           /* 复用推挽输出 */GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;                         /* 50MHz*/GPIO_Init(GPIOA,&GPIO_InitStruct);                                   /* 初始化GPIOA */GPIO_InitStruct.GPIO_Pin=GPIO_Pin_10;                                /* PA3引脚 ,USART的RX*/GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IN_FLOATING;                     /* 浮空输入,用来接收数据 */GPIO_Init(GPIOA,&GPIO_InitStruct);                                   /* 初始化GPIOA */
}void USART1_Config()
{USART_InitTypeDef USART_InitStructure;                                 /* 结构体定义 */USART_InitStructure.USART_BaudRate = 115200;                           /* 配置波特率 */USART_InitStructure.USART_WordLength = USART_WordLength_8b;            /* 8位数据 */USART_InitStructure.USART_StopBits = USART_StopBits_1;                 /* 1位停止位 */USART_InitStructure.USART_Parity = USART_Parity_No ;                   /* 无奇偶校验 */USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;/* 无硬件流控制 */USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;        /* 允许接收和发送 */USART_Init(USART1, &USART_InitStructure);                              /* 完成串口初始化 */USART_Cmd(USART1, ENABLE);                                             /* 串口使能 */}//重定义printf函数
int fputc(int ch, FILE *f)
{/* 将Printf内容发往串口 */USART_SendData(USART1, (unsigned char)ch);while (!USART_GetFlagStatus(USART1, USART_FLAG_TC));USART_ClearFlag(USART1, USART_FLAG_TC);return (ch);}int fgetc(FILE *f)
{/* 等待串口输入数据 */while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == RESET);return (int)USART_ReceiveData(USART1);
}

四、运行结果

通过编译调试后,使用串口监听运行结果如下:

五、结论

当SPI时钟频率较高时,建议采用DMA模式以避免SPI速度性能的降低,且SPI有一个缺点:有指定的流控制,没有应答机制确认是否接收到了数据,不像I2C没传输数据会有一个ACK应答机制,学习时序弄清底层逻辑尤为重要,不可得过且过。

SPI读写FLASH 原理+完整代码相关推荐

  1. STM32笔记(十二)---SPI读写FLASH

    SPI读写FLASH 文章目录 SPI读写FLASH 一.SPI协议简介 1.1 SPI 物理层 1.2 协议层 1.2.1 SPI 基本通讯过程 1.2.2 通讯的起始和停止信号 1.2.3 数据有 ...

  2. 《STM32从零开始学习历程》——SPI读写FLASH

    <STM32从零开始学习历程>@EnzoReventon SPI读写FLASH 相关链接: SPI物理层及FLASH芯片介绍 SPI协议层 SPI特性及架构 参考资料: [野火EmbedF ...

  3. STM32CUBEIDE之SPI读写FLASH进阶串行FLASH文件系统FatFs

    预备知识 >>W25Q128是16M spi flash,一共有256个block ,每个Block 64KB. >>一个Block可以分割为16个扇区(small secto ...

  4. STM32F103学习笔记——SPI读写Flash(二)

      此系列文章是小白学习STM32的一些学习笔记.小白第一次写笔记文章,有不足或是错误之处,请多体谅和交流! 目录 1.软件设计流程 2.SPI初始化 3.SPI发送接收一字节函数编写 4.FLASH ...

  5. STM32F103配合STM32CubeMX实现SPI读写flash

    本人采用的是正点原子的精英STM32F103开发板,其包含一块W25Q128型号的flash芯片.该flash与STM32F103的SPI2相连. 下面根据正点原子提供的开发指南文档,实现FreeRT ...

  6. STM32F429入门(二十一):SPI协议及SPI读写FLASH

    IIC主要用于通讯速率一般的场合,而SPI一般用于较高速的场合. 一.SPI协议简介 SPI 协议是由摩托罗拉公司提出的通讯协议(Serial Peripheral Interface),即串行外围设 ...

  7. STM32 SPI读写FLASH

    文章目录 一.SPI协议 1.物理层 2.协议层 总体讲解 具体讲解 二.STM32 SPI外设 1.通讯引脚 2.时钟控制逻辑 3.数据控制 4.整体控制逻辑 三.通信过程 四.固件库编程 1.结构 ...

  8. AXI Quad SPI读写Flash做远程升级

    未经允许,本文禁止转载 目录 简介 AXI Quad SPI IP设置 寄存器说明 AXI Quad SPI支持的通用命令 读flash id 读flash 数据 擦除扇区 写flash 数据 注意事 ...

  9. stm32中spi可以随便接吗_STM32的SPI模式读写FLASH芯片全面讲解

    例程完整代码: SPI协议简介 SPI协议,即串行外围设备接口,是一种告诉全双工的通信总线,它被广泛地使用在ADC,LCD等设备与MCU间通信的场合. SPI信号线 SPI包含4条总线,分别为SS,S ...

最新文章

  1. unittest 框架学习
  2. react 从零开始搭建开发环境
  3. 前端框架MVC/MVVM分析系列
  4. BZOJ1305: [CQOI2009]dance跳舞
  5. api 另一窗体 之上_12 个设计 API 的安全建议,不要等出事儿了“捶胸顿足”
  6. spring cloud中gateway存在的意义是什么?
  7. Python格式化字符串字面值 | 被官方文档称之为『漂亮』的输出格式
  8. 如何设置程序默认“以管理员身份运行”
  9. java web 邮件_Java Web(十二) JavaMail发送邮件
  10. Idea中使用maven命令
  11. 电路串联和并联图解_判断串联并联电路图口诀
  12. Linux程序动态库的加载
  13. python 批量读取csv_Python Pandas批量读取csv文件到dataframe的方法
  14. c语言实参和形参占用存储单元_在C语言中,以下说法正确的是()。 A.实参和与其对应的形参分别占用独立的存储单元。 B.实参和与...
  15. 阅读类APP开发的好处有哪些
  16. 获取店铺商品详情和订单详情
  17. Bert几个数据集的概念Cola、MRPC、XNLI、MNLI等
  18. php中左移和右移,c语言左移和右移的示例详解
  19. 多项式分解 java
  20. 【mysql新加不了中文】Error Code: 1366. Incorrect string value: ‘\xE7\xBA\xB8\xE7\xB1\xBB‘ for colum

热门文章

  1. Win7Codecs+设置程序中英文对照
  2. 区块链在司法存证领域的应用报告 | 陀螺研究院
  3. 常用数学符号 希腊字母
  4. pandas学习笔记之DateFrame
  5. 我,程序员,32岁失业后干啥都赔钱,月薪2万的好日子一去不返
  6. Python安装第三方库的常用方法:使用pip
  7. 华测P550数据导入睿铂Skyscanner工作流程
  8. 小车高速怎么收费标准_2018小轿车高速路收费标准 私家车走高速怎么收费
  9. 监控之星-普罗米修斯Prometheus搭建
  10. win系统一键安装redmine+配置+插件安装配置教程【原创-亲测安装成功-一枚测试喵】