[STM32 - 野火] - - - 固件库学习笔记 - - -九.SPI

1 SPI协议简介

SPI(Serial Peripheral Interface,串行外围设备接口)是一种高速、全双工、同步的串行通信接口,它由Motorola公司于20世纪80年代开发。主要用于微控制器和其他外围设备之间的通信,比如传感器、存储器、ADC、DAC等。

SPI协议因其简单、高效和易于实现,在嵌入式系统中非常流行。不过,它也有一些局限性,比如它不支持设备寻址,所有设备共享相同的通信路线,因此不适合想要多个主设备的应用。

1.1 物理层

SPI通讯设备之间的常见连接方式如下:
在这里插入图片描述
在物理层,SPI协议使用以下几种信号线:

  • MISO(Master Input Slave Output):主设备输入/从设备输出引脚。

主机从这条信号线读入数据,从机的数据由这条信号线输出到主机。

  • MOSI(Master Output Slave Input):主设备输出/从设备输入引脚。

主机的数据从这条信号线输出,从机由这条信号线读入主机发送的数据。

  • SCK(Serial Clock):时钟信号线,用于通讯数据同步。

它由通讯主机产生,决定了通讯的速率,不同的设备支持的最高时钟频率不一样,两个设备之间通讯时,通讯速率受限于低速设备。

  • SS(Slave Select):从设备选择信号线,常称为片选信号线,也称为 NSS、 CS。

当SS线为低电平时,从设备被选中,可以参与通信;当SS先为高电平时,从设备被禁用,无法进行通信。(当SS为高电平时,从设备会忽略三根线上传输的数据)

当有多个 SPI 从设备与 SPI 主机相连时,设备的其它信号线 SCK、 MOSI 及 MISO 同时并联到相同的 SPI 总线上,即无论有多少个从设备,都共同只使用这 3 条总线;每个从设备都有独立的这一条 SS 信号线,SS信号线独占主机的一个引脚,即有多少个从设备,就有多少条片选信号线。

1.2 协议层

协议层定义了通信的起始信号、停止信号、数据的有效性、时钟同步等环节。

1.2.1 基本通讯过程

在这里插入图片描述

上图为SPI通讯的一种通讯时序,一个主机的通讯时序。

SS、 SCK、 MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。

在这个时序图中,SCK上升沿触发(数据产生变化),SCK下降沿采样,在采样过程中的数据是稳定的,此时MOSI、MISO上的电平信号就代表了一个数据位的数据。

1.2.2 起始信号与结束信号

起始信号:SS信号线由高电平变为低电平。

结束信号:SS信号线由低电平变为高电平。

1.2.3 数据有效性

MOSI 及 MISO 数据线在 SCK 的每个时钟周期传输一位数据,且数据输入输出是同时进行的。

数据传输时, MSB先行或 LSB 先行并没有作硬性规定,但要保证两个 SPI 通讯设备之间使用同样的协定。

数据在采样过程中有效(以上图中时序为例,则下降沿的数据有效),在其他时刻,数据无效。

SPI 每次数据传输可以 8 位或 16 位为单位,可以根据实际需要传输任意位数的数据。

在实际应用中,SPI通讯会涉及到固定大小的数据块,比如一个字节或更多字节,这取决于具体的数据手册和通信协议。我们需要参考与主机通信设备的数据手册来确定数据传输的大小。

1.2.4 CPOL/CPHA及通讯模式

CPOL(Clock Polarity)和CPHA(Clock Phase)是SPI时序的两个重要参数,它们决定了数据传输的时钟极性和相位。

  • CPOL(Clock Polarity):定义了时钟信号在空闲时的电平。

    • 当CPOL为0时,时钟信号在空闲时为低电平。
    • 当CPOL为1时,时钟信号在空闲时为高电平。
  • CPHA(Clock Phase):定义了数据采样的时机。它决定了在时钟信号的哪个边沿上数据被采样以及何时数据被放置到总线上。

    • 当CPHA为0时,数据在时钟信号的第一个边沿(上升沿或下降沿)被采样,并在下一个边沿上被放置到总线上。

    当CPHA等于0时,MOSI、MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。
    在这里插入图片描述

    • 当CPHA为1时,数据在时钟信号的第二个边沿被采样,并在下一个边沿上被放置到总线上。

    当CPHA等于1时,MOSI、MISO数据线上的信号将会在SCK时钟线的“偶数边沿”被采样。
    在这里插入图片描述

基于CPOL(Clock Polarity)和CPHA(Clock Phase)参数的不同组合,SPI定义了以下四种工作模式:其中采样较为广泛的是Mode0与Mode3。

主机与从机需要工作在相同的模式下才能正常通讯。

  • Mode 0:

    • CPOL = 0, CPHA = 0
    • 时钟信号在空闲时为低电平,数据在时钟的上升沿(时钟从低到高的过渡)采样,并在下降沿(时钟从高到低的过渡)放置到总线上。
  • Mode 1:

    • CPOL = 0, CPHA = 1
    • 时钟信号在空闲时为低电平,数据在时钟的下降沿采样,并在上升沿放置到总线上。
  • Mode 2:

    • CPOL = 1, CPHA = 0
    • 时钟信号在空闲时为高电平,数据在时钟的上升沿采样,并在下降沿放置到总线上。
  • Mode 3:

    • CPOL = 1, CPHA = 1
    • 时钟信号在空闲时为高电平,数据在时钟的下降沿采样,并在上升沿放置到总线上。
ModeCPOLCPHA空闲时SCK时钟采样时刻
000低电平奇数边沿
101低电平偶数边沿
210高电平奇数边沿
311高电平偶数边沿

CPOL与CPHA可通过SPI_CR1寄存器的位0与位1进行配置:

在这里插入图片描述

2 SPI框图

在这里插入图片描述

SPI框图可分为四个部分:通讯引脚、时钟控制逻辑、数据控制逻辑与整体控制逻辑。

2.1 框图介绍

  • 1.通讯引脚

在这里插入图片描述

其中,SPI1是APB2上的设备,最高通讯速率为36Mbit/s,SPI2、SPI3是APB1上的设备,最高通讯速率为18Mbit/s。

实际应用中,NSS引脚一般使用GPIO引脚(配置为推挽输出模式),通过软件进行控制它的电平输出,从而产生起始信号与结束信号。

SPI2与SPI3可以作为I2S通讯,这个主要用于传输音频数据。( I2S通讯与SPI通讯类似。)
注意:如果使用的是SPI3,会占用烧录引脚,这时要进行烧录的话就要按着复位键进行烧录。

在这里插入图片描述

上图为 《STM32F103xCDE_数据手册》 中 表2 STM32F103xC、 STM32F103xD和STM32F103xE器件功能和配置 的一部分。

  • 2.时钟控制逻辑
    • 时钟控制逻辑(波特率发生器)通过SPI_CR1寄存器的位5:3(BR[2:0])三个数据位进行配置(产生的时钟频率)。

在这里插入图片描述

其中,fPCLK为SPI所在的APB总线频率,挂载在APB1总线上则为fAPB1,挂载在APB2总线上则为fAPB2

  • 3.数据控制逻辑

    • SPI的MOSI与MISO都连接到数据移位寄存器上,数据移位寄存器的数据来源于接收缓冲区与发送缓冲区。

    有两个数据移位寄存器,一个用来接收数据,一个用来发送数据。

    • 发送数据时数据寄存器DR会把数据放到发送缓冲区中。

    • 接收数据时数据寄存器DR会读取接收缓冲区中的数据。

    • 数据帧长度可通过SPI_CR1寄存器的位11 DFF进行配置:
      在这里插入图片描述

    • 帧格式可通过SPI_CR1寄存器的位7进行配置:
      在这里插入图片描述

  • 4.整体控制逻辑

    • 整体控制逻辑负责协调整个SPI外设,控制逻辑的工作模式根据SPI_CR1、SPI_CR2寄存器的参数而改变。
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
    • 在外设工作时,控制逻辑会根据外设的工作状态修改SPI_SR寄存器,我们只要读取状态寄存器相关的寄存器位,就可以了解 SPI 的工作状态了。除此之外,控制逻辑还根据要求,负责控制产生 SPI 中断信号、 DMA 请求及控制 NSS 信号线。
      在这里插入图片描述
      在这里插入图片描述

很少SPI设备支持CRC校验。

2.2 通讯过程

在这里插入图片描述

上图为STM32作为主机的一个时序图。在整个通讯过程中,我们根据SPI_SR寄存器的标志位来进行协调。

从上图中,我们可以看到:BSY标志与SS引脚的电平相反。可通过BSY标志来判断SPI是否正在通信或发送缓冲区是否为空。

2.2.1 发送数据

  • 1.控制STM32向SPI_DR寄存器中写入数据0xF1。

  • 2.当数据写入到发送缓冲区时,TXE标志清0表非发送缓冲区空。

  • 3.SPI_DR寄存器中的数据被搬运到数据移位寄存器后,发送缓冲区为空,TXE置1。

  • 4.向SPI_DR寄存器写入第二个数据0xF2。

如果数据没被发送就一直放在SPI_DR寄存器中,等待TXE置1后将数据搬运到数据移位寄存器中。

简单来说,发送数据的过程就是:往SPI_DR寄存器中写入数据 -> 等待TXE置1 -> 写入下一个数据。

2.2.2 接收数据

  • 1.接收到一帧数据后,RXNE标志位置1(表示接收缓冲区非空)。

  • 2.读取SPI_DR寄存器获取接收缓冲区中的数据。

注意:如果你只是要接收数据,那么在接收数据的时候你也要往移位发送器中写入数据,以此让STM32产生时钟。

假如使能了TXE或RXNE中断,TXE或RXNE置1时会产生SPI中断信号,进入统一规格中断服务函数。进入到SPI中断服务程序后,可通过检测寄存器位来了解是哪一个时间,再分别进行处理。也可以使用DMA方式来收发SPI_DR寄存器中的数据。

3 SPI初始化结构体

/** 
  * @brief  SPI Init structure definition  
  */

typedef struct
{
  uint16_t SPI_Direction;           /*!< 设置 SPI 的单双向模式 */

  uint16_t SPI_Mode;                /*!< 设置 SPI 的主/从机端模式  */

  uint16_t SPI_DataSize;            /*!< 设置 SPI 的数据帧长度,可选 8/16 位 */

  uint16_t SPI_CPOL;                /*!< 设置时钟极性 CPOL,可选高/低电平 */

  uint16_t SPI_CPHA;                /*!< 设置时钟相位,可选奇/偶数边沿采样 */

  uint16_t SPI_NSS;                 /*!< 设置 NSS 引脚由 SPI 硬件控制还是软件控制 */
 
  uint16_t SPI_BaudRatePrescaler;   /*!< 设置时钟分频因子, fpclk/分频数 =fSCK  */

  uint16_t SPI_FirstBit;            /*!< 设置 MSB/LSB 先行 */

  uint16_t SPI_CRCPolynomial;       /*!< 设置 CRC 校验的表达式 */
}SPI_InitTypeDef;
  • SPI_Direction:设置 SPI 的通讯方向。
/** @defgroup SPI_data_direction 
  * @{
  */
  
#define SPI_Direction_2Lines_FullDuplex ((uint16_t)0x0000)  // 双线全双工
#define SPI_Direction_2Lines_RxOnly     ((uint16_t)0x0400)  // 双线只接收
#define SPI_Direction_1Line_Rx          ((uint16_t)0x8000)  // 单线只接收
#define SPI_Direction_1Line_Tx          ((uint16_t)0xC000) // 单线只发送
#define IS_SPI_DIRECTION_MODE(MODE) (((MODE) == SPI_Direction_2Lines_FullDuplex) || \
                                     ((MODE) == SPI_Direction_2Lines_RxOnly) || \
                                     ((MODE) == SPI_Direction_1Line_Rx) || \
                                     ((MODE) == SPI_Direction_1Line_Tx))
  • SPI_Mode:设置 SPI 工作在主机模式 (SPI_Mode_Master) 或从机模式 (SPI_Mode_Slave )

若被配置为从机模式, STM32 的 SPI 外设将接受外来的 SCK 信号。

/** @defgroup SPI_mode 
  * @{
  */

#define SPI_Mode_Master                 ((uint16_t)0x0104)	// 主机模式
#define SPI_Mode_Slave                  ((uint16_t)0x0000)	// 从机模式
#define IS_SPI_MODE(MODE) (((MODE) == SPI_Mode_Master) || \
                           ((MODE) == SPI_Mode_Slave))
  • SPI_DataSize:可以选择 SPI 通讯的数据帧大小是为 8 位 (SPI_DataSize_8b) 还是 16 位 (SPI_DataSize_16b)。
/** @defgroup SPI_data_size 
  * @{
  */

#define SPI_DataSize_16b                ((uint16_t)0x0800)	//  16 位
#define SPI_DataSize_8b                 ((uint16_t)0x0000)	//  8 位
#define IS_SPI_DATASIZE(DATASIZE) (((DATASIZE) == SPI_DataSize_16b) || \
                                   ((DATASIZE) == SPI_DataSize_8b))
  • SPI_CPOL :配置 SPI 的时钟极性。
/** @defgroup SPI_Clock_Polarity 
  * @{
  */

#define SPI_CPOL_Low                    ((uint16_t)0x0000)	// 低电平
#define SPI_CPOL_High                   ((uint16_t)0x0002)	// 高电平
#define IS_SPI_CPOL(CPOL) (((CPOL) == SPI_CPOL_Low) || \
                           ((CPOL) == SPI_CPOL_High))
  • SPI_CPHA:配置 SPI 的时钟相位。
/** @defgroup SPI_Clock_Phase 
  * @{
  */

#define SPI_CPHA_1Edge                  ((uint16_t)0x0000)	// 在 SCK 的奇数边沿采集数据
#define SPI_CPHA_2Edge                  ((uint16_t)0x0001)	// 在 SCK 的偶数边沿采集数据
#define IS_SPI_CPHA(CPHA) (((CPHA) == SPI_CPHA_1Edge) || \
                           ((CPHA) == SPI_CPHA_2Edge))
  • SPI_NSS:配置 NSS 引脚的使用模式。

硬件模式中的 SPI 片选信号由 SPI 硬件自动产生,而软件模式则需要我们亲自把相应的 GPIO 端口拉高或置低产生非片选和片选信号。

/** @defgroup SPI_Slave_Select_management 
  * @{
  */

#define SPI_NSS_Soft                    ((uint16_t)0x0200)	// 软件模式
#define SPI_NSS_Hard                    ((uint16_t)0x0000)	// 硬件模式
#define IS_SPI_NSS(NSS) (((NSS) == SPI_NSS_Soft) || \
                         ((NSS) == SPI_NSS_Hard))
  • SPI_BaudRatePrescaler:设置波特率分频因子,分频后的时钟即为 SPI 的 SCK 信号线的时钟频率。
/** @defgroup SPI_BaudRate_Prescaler 
  * @{
  */

#define SPI_BaudRatePrescaler_2         ((uint16_t)0x0000)
#define SPI_BaudRatePrescaler_4         ((uint16_t)0x0008)
#define SPI_BaudRatePrescaler_8         ((uint16_t)0x0010)
#define SPI_BaudRatePrescaler_16        ((uint16_t)0x0018)
#define SPI_BaudRatePrescaler_32        ((uint16_t)0x0020)
#define SPI_BaudRatePrescaler_64        ((uint16_t)0x0028)
#define SPI_BaudRatePrescaler_128       ((uint16_t)0x0030)
#define SPI_BaudRatePrescaler_256       ((uint16_t)0x0038)
#define IS_SPI_BAUDRATE_PRESCALER(PRESCALER) (((PRESCALER) == SPI_BaudRatePrescaler_2) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_4) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_8) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_16) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_32) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_64) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_128) || \
                                              ((PRESCALER) == SPI_BaudRatePrescaler_256))
  • SPI_FirstBit:设置MSB 先行 (高位数据在前) 还是 LSB 先行 (低位数据在前)。
/** @defgroup SPI_MSB_LSB_transmission 
  * @{
  */

#define SPI_FirstBit_MSB                ((uint16_t)0x0000)
#define SPI_FirstBit_LSB                ((uint16_t)0x0080)
#define IS_SPI_FIRST_BIT(BIT) (((BIT) == SPI_FirstBit_MSB) || \
                               ((BIT) == SPI_FirstBit_LSB))
  • SPI_CRCPolynomial:SPI 的 CRC 校验中的多项式。

当我们使用 CRC 校验时,就使用这个成员的参数 (多项式),来计算 CRC 的值。

4 读写串行FLASH

4.1 FLASH简介

在这里插入图片描述
引脚:

  • DO:MISO。

  • DIO:MOSI。

  • CS:片选SS(低电平有效)。

  • WP:输入写保护(低电平有效)。

  • HOLD:暂停通讯;如果SPI正在通讯,将这个引脚拉低后可暂停通讯。

FLASH的存储特性:

  • 在写入数据前必须先擦除

  • 擦除时会把数据位全重置为1

  • 写入数据时只能把为1的数据位改为0

  • 擦除时必须按最小单位来擦除(一般最小单位为扇区)

    想要擦除某一扇区,发送该扇区的最低位地址

norflash可以一个字节写入,nandflash必须以块或扇区为单位。

4.2 硬件SPI

4.2.1 步骤

  • 1.初始化通讯使用的目标引脚及端口时钟;

  • 2.使能 SPI 外设的时钟;

  • 3.配置 SPI 外设的模式、地址、速率等参数并使能 SPI 外设;

  • 4.编写基本 SPI 按字节收发的函数;

  • 5.编写对 FLASH 擦除及读写操作的的函数;

  • 6.编写测试程序,对读写数据进行校验。

4.2.2 初始化

// bsp_spi.h
#define  DEBUG_SPIx                   SPI1
#define  DEBUG_SPI_CLK                RCC_APB2Periph_SPI1
#define  DEBUG_SPI_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_SPI_GPIO_CLK						(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA)

// SPI GPIO 引脚宏定义

/*CS(NSS)*/
#define  DEBUG_SPI_CS_GPIO_PORT       		GPIOC   
#define  DEBUG_SPI_CS_GPIO_PIN        		GPIO_Pin_0
#define  DEBUG_SPI_CS_GPIO_CLK      		  RCC_APB2Periph_GPIOC
#define  DEBUG_SPI_CS_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*SCLK*/
#define  DEBUG_SPI_SCLK_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_SCLK_GPIO_PIN        		GPIO_Pin_5
#define  DEBUG_SPI_SCLK_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_SCLK_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*MISO*/
#define  DEBUG_SPI_MISO_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_MISO_GPIO_PIN        		GPIO_Pin_6
#define  DEBUG_SPI_MISO_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_MISO_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*MOSI*/
#define  DEBUG_SPI_MOSI_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_MOSI_GPIO_PIN        		GPIO_Pin_7
#define  DEBUG_SPI_MOSI_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_MOSI_GPIO_APBxClock			RCC_APB2PeriphClockCmd

#define  SPI_CS_HIGH()		GPIO_SetBits( DEBUG_SPI_CS_GPIO_PORT, DEBUG_SPI_CS_GPIO_PIN )
#define  SPI_CS_LOW()		GPIO_ResetBits( DEBUG_SPI_CS_GPIO_PORT, DEBUG_SPI_CS_GPIO_PIN )
// bsp_spi.c
void SPI_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 打开SPI GPIO的时钟
	RCC_APB2PeriphClockCmd(DEBUG_SPI_GPIO_CLK, ENABLE);
	
	// 打开SPI 外设的时钟
	RCC_APB2PeriphClockCmd(DEBUG_SPI_CLK, ENABLE);
	
	// 将 SCLK 的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_SCLK_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_SCLK_GPIO_PORT, &GPIO_InitStructure);
	
	// 将 MOSI 的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_MOSI_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
	
	// 将 MISO 的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_MISO_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
	
	// 初始化CS引脚,使用软件控制,所以直接设置为推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_CS_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
	
	SPI_CS_HIGH();
	
}

void SPI_MODE_Config(void)
{
	SPI_InitTypeDef SPI_InitSturcture;
	
	// 配置为模式3
	SPI_InitSturcture.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;	// 波特率预分频值为2
	SPI_InitSturcture.SPI_CPHA = SPI_CPHA_2Edge;												// 模式3下CPHA数据捕获于第二个时钟沿(偶数沿)
	SPI_InitSturcture.SPI_CPOL = SPI_CPOL_High;													// 模式3下CPOL时钟悬空为高
	SPI_InitSturcture.SPI_CRCPolynomial = 0; 														// 不使用CRC功能,数值随意
	SPI_InitSturcture.SPI_DataSize = SPI_DataSize_8b;										// SPI发送接收8位帧结构
	SPI_InitSturcture.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	// 设置为双线全双工
	SPI_InitSturcture.SPI_FirstBit = SPI_FirstBit_MSB; 									// 看芯片是MSB先行还是LSB先行
	SPI_InitSturcture.SPI_Mode = SPI_Mode_Master;												// 设置为主SPI
	SPI_InitSturcture.SPI_NSS = SPI_NSS_Soft;														// 软件控制NSS(CS)引脚
	
	SPI_Init(DEBUG_SPIx, &SPI_InitSturcture);
	
	SPI_Cmd(DEBUG_SPIx, ENABLE);		// 使能SPI
}

void SPI_Config(void)
{
	SPI_GPIO_Config();
	
	SPI_MODE_Config();
}

4.2.3 SPI基本功能函数

4.2.3.1 发送一个字节
// 发送一个字节
uint8_t SPI_Send_Byte(uint8_t data)
{
	uint16_t SPI_TIME_OUT = SPIT_FLAG_TIMEOUT;
	// 检查并等待至 TX缓冲区 为空
	while(SPI_I2S_GetFlagStatus(DEBUG_SPIx, SPI_I2S_FLAG_TXE) == RESET)
	{
		if((SPI_TIME_OUT--) == 0) return SPI_TIMEOUT_UserCallback(0);
	}
	
	// 程序执行到此处说明 TX缓冲区 已空
	SPI_I2S_SendData(DEBUG_SPIx, data);
	
	SPI_TIME_OUT = SPIT_FLAG_TIMEOUT;
	// 检查并等待至 RX缓冲区 为非空
	while(SPI_I2S_GetFlagStatus(DEBUG_SPIx, SPI_I2S_FLAG_RXNE) == RESET)
	{
		if((SPI_TIME_OUT--) == 0) return SPI_TIMEOUT_UserCallback(1);
	}
	/* 同步收发,一字节发送完毕说明一字节也接收完毕*/
	
	// 程序执行到此处说明 RX缓冲区 非空
	return SPI_I2S_ReceiveData(DEBUG_SPIx);
}

这里注意检查缓冲区数据是否为空的函数不要写错。

请添加图片描述

上图是在测试SPI_Send_Byte函数时遇到的一个问题:SPI等待超时。

原因:函数名写错了造成SPI通讯失败。

请添加图片描述

4.2.3.2 读取一个字节
// 读取一个字节
uint8_t SPI_Read_Byte(void)
{
	/* flash会根据你写的命令来选择是否忽略后面发来的数据 */
	return SPI_Send_Byte(DUMMY); // 这里也可以发送0xff,flash会忽略这个数据
}
4.2.3.3 读取数据
// 读取flahs id 
uint32_t SPI_Read_ID(void)
{
	/* 根据芯片手册,发送读取id指令,芯片会返回三个字节数据*/
	uint32_t flash_id;
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_JEDEC_ID);
	
	flash_id = SPI_Send_Byte(DUMMY);				// 接收数据
	
	flash_id <<= 8;
	
	flash_id |= SPI_Send_Byte(DUMMY);
	
	flash_id <<= 8;
	
	flash_id |= SPI_Send_Byte(DUMMY);
	
	SPI_CS_HIGH();
	
	return flash_id;
}

// 读取FLASH的内容
void SPI_Read_Data(uint32_t addr, uint8_t *data, uint32_t numByteToRead)
{
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_DATA);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	while(numByteToRead--)
	{
		*data = SPI_Send_Byte(DUMMY);
		data++;
	}
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

在这里插入图片描述

在这里插入图片描述

4.2.3.4 写数据

写入数据之前必须要加擦除操作。

// FLASH写入使能
void SPI_Write_Enable(void)
{
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(WRITE_ENABLE);
	
	SPI_CS_HIGH();
}

// 向FLASH写入内容,最多写入256字节
void SPI_Write_Data(uint32_t addr, uint8_t *writeBuff, uint32_t numByteToRead)
{
	SPI_Write_Enable();
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(WRITE_DATA);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	while(numByteToRead--)
	{
		SPI_Send_Byte(*writeBuff);
		writeBuff++;
	}
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

void SPI_BufferWrite(uint32_t WriteAddr, uint8_t* pBuffer, uint16_t NumByteToWrite)
{
	uint8_t 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 刚好按页对齐*/
	if(Addr == 0)
	{
		/* NumByteToWrite < SPI_FLASH_PageSize */
		if(NumOfPage == 0)
		{
			SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
		}
		else	/* NumByteToWrite > SPI_FLASH_PageSize */
		{
			/* 先把整数页都写了 */
			while(NumOfPage--)
			{
				SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
				WriteAddr = WriteAddr + SPI_FLASH_PageSize;
				pBuffer = pBuffer + SPI_FLASH_PageSize;
			}
			/* 若有多余的不满一页的数据,先把它写完 */
			SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
		}
	}
	/* 若地址与SPI_FLASH_PageSize不对齐*/
	else
	{
		/* NumByteToWrite < SPI_FLASH_PageSize */
		if(NumOfPage == 0)
		{
			/* 当前页剩余的count个位置比NumOfSingle小,一页写不完 */
			if(NumOfSingle > count)
			{
				temp = NumOfSingle - count;
				/* 先写满当前页*/
				SPI_Write_Data(WriteAddr, pBuffer, count);
				
				WriteAddr = WriteAddr + count;
				pBuffer = pBuffer + count;
				/* 再写剩余的数据 */
				SPI_Write_Data(WriteAddr, pBuffer, temp);
			}
			else  /* 当前页剩余的count个位置能写完NumOfSingle个数据 */
			{
				SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
			}
		}
		else  /* NumByteToWrite > SPI_FLASH_PageSize */
		{
			/* 地址不对齐多出的count分开处理,不加入这个运算*/
			NumByteToWrite -= count;
			NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
			NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
			SPI_Write_Data(WriteAddr, pBuffer, count);
			
			/* 接下来就重复地址对齐的情况*/
			WriteAddr += count;
			pBuffer += count;
			
			/* 把整数页都写了 */
			while(NumOfPage--)
			{
				SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
				WriteAddr = WriteAddr + SPI_FLASH_PageSize;
				pBuffer = pBuffer + SPI_FLASH_PageSize;
			}
			
			/* 若有对于的不满一页的数据,把它写完 */
			if(NumOfSingle != 0)
			{
				SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
			}
		}
	}
}

在这里插入图片描述
在这里插入图片描述

4.2.3.5 等待FLASH内部时序操作完成
void SPI_WaitForWriteEnd(void)
{
	uint8_t status_reg = 0;
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_STATUS);
	
	do
	{
		status_reg = SPI_Send_Byte(DUMMY);
	}while((status_reg & 0x01) == 1);
	
	SPI_CS_HIGH();
}

在这里插入图片描述

4.2.3.6 擦除
// 擦除FLASH指定扇区
void SPI_Erase_Sector(uint32_t addr)
{
	SPI_Write_Enable(); // FLASH写入数据前要先发送写使能命令,擦除也算写操作
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(SECTOR_ERASE);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

擦除指令一般使用Sector Erase或Chip Erase。
这样擦除会有一个缺陷:如果FLASH本身内部就没有写入数据,那么就验证不了我们写的函数是否起到了效果。

在这里插入图片描述

4.2.4 整合

// bsp_spi.h
#ifndef __BSP_SPI_H
#define __BSP_SPI_H

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

#define  DEBUG_SPIx                   SPI1
#define  DEBUG_SPI_CLK                RCC_APB2Periph_SPI1
#define  DEBUG_SPI_APBxClkCmd         RCC_APB2PeriphClockCmd
#define  DEBUG_SPI_GPIO_CLK						(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOA)

// SPI GPIO 引脚宏定义

/*CS(NSS)*/
#define  DEBUG_SPI_CS_GPIO_PORT       		GPIOC   
#define  DEBUG_SPI_CS_GPIO_PIN        		GPIO_Pin_0
#define  DEBUG_SPI_CS_GPIO_CLK      		  RCC_APB2Periph_GPIOC
#define  DEBUG_SPI_CS_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*SCLK*/
#define  DEBUG_SPI_SCLK_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_SCLK_GPIO_PIN        		GPIO_Pin_5
#define  DEBUG_SPI_SCLK_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_SCLK_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*MISO*/
#define  DEBUG_SPI_MISO_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_MISO_GPIO_PIN        		GPIO_Pin_6
#define  DEBUG_SPI_MISO_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_MISO_GPIO_APBxClock			RCC_APB2PeriphClockCmd

/*MOSI*/
#define  DEBUG_SPI_MOSI_GPIO_PORT       		GPIOA   
#define  DEBUG_SPI_MOSI_GPIO_PIN        		GPIO_Pin_7
#define  DEBUG_SPI_MOSI_GPIO_CLK      		  RCC_APB2Periph_GPIOA
#define  DEBUG_SPI_MOSI_GPIO_APBxClock			RCC_APB2PeriphClockCmd

#define  SPI_CS_HIGH()		GPIO_SetBits( DEBUG_SPI_CS_GPIO_PORT, DEBUG_SPI_CS_GPIO_PIN )
#define  SPI_CS_LOW()		GPIO_ResetBits( DEBUG_SPI_CS_GPIO_PORT, DEBUG_SPI_CS_GPIO_PIN )

#define SPIT_FLAG_TIMEOUT         ((uint32_t)0x1000)

/*信息输出*/
#define EEPROM_DEBUG_ON         1

#define EEPROM_INFO(fmt,arg...)           printf("<<-EEPROM-INFO->> "fmt"\n",##arg)
#define EEPROM_ERROR(fmt,arg...)          printf("<<-EEPROM-ERROR->> "fmt"\n",##arg)
#define EEPROM_DEBUG(fmt,arg...)          do{\
                                          if(EEPROM_DEBUG_ON)\
                                          printf("<<-EEPROM-DEBUG->> [%s][%d]"fmt"\n",__FILE__,__LINE__, ##arg);\
                                          }while(0)

#define DUMMY 			  0x00
#define READ_JEDEC_ID 0x9f
#define SECTOR_ERASE	0x20
#define READ_STATUS		0x05
#define READ_DATA     0x03
#define WRITE_ENABLE  0x06
#define WRITE_DATA    0x02
																					
#define SPI_FLASH_PageSize 256
																					
void SPI_Config(void);
uint32_t SPI_Read_ID(void);
void SPI_Erase_Sector(uint32_t addr);																					
void SPI_Read_Data(uint32_t addr, uint8_t *data, uint32_t numByteToRead);
void SPI_WaitForWriteEnd(void);
void SPI_Write_Data(uint32_t addr, uint8_t *writeBuff, uint32_t numByteToRead);
void SPI_BufferWrite(uint32_t WriteAddr, uint8_t* pBuffer, uint16_t NumByteToWrite);
																					
#endif /* __BSP_SPI_H */

// bsp_spi.c
#include "bsp_spi.h"

static  uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);

void SPI_GPIO_Config(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 打开SPI GPIO的时钟
	RCC_APB2PeriphClockCmd(DEBUG_SPI_GPIO_CLK, ENABLE);
	
	// 打开SPI 外设的时钟
	RCC_APB2PeriphClockCmd(DEBUG_SPI_CLK, ENABLE);
	
	// 将 SCLK 的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_SCLK_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_SCLK_GPIO_PORT, &GPIO_InitStructure);
	
	// 将 MOSI 的GPIO配置为推挽复用模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_MOSI_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
	
	// 将 MISO 的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_MISO_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_MISO_GPIO_PORT, &GPIO_InitStructure);
	
	// 初始化CS引脚,使用软件控制,所以直接设置为推挽输出
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = DEBUG_SPI_CS_GPIO_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
	
	SPI_CS_HIGH();
	
}

void SPI_MODE_Config(void)
{
	SPI_InitTypeDef SPI_InitSturcture;
	
	// 配置为模式3
	SPI_InitSturcture.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;	// 波特率预分频值为2
	SPI_InitSturcture.SPI_CPHA = SPI_CPHA_2Edge;												// 模式3下CPHA数据捕获于第二个时钟沿(偶数沿)
	SPI_InitSturcture.SPI_CPOL = SPI_CPOL_High;													// 模式3下CPOL时钟悬空为高
	SPI_InitSturcture.SPI_CRCPolynomial = 0; 														// 不使用CRC功能,数值随意
	SPI_InitSturcture.SPI_DataSize = SPI_DataSize_8b;										// SPI发送接收8位帧结构
	SPI_InitSturcture.SPI_Direction = SPI_Direction_2Lines_FullDuplex;	// 设置为双线全双工
	SPI_InitSturcture.SPI_FirstBit = SPI_FirstBit_MSB; 									// 看芯片是MSB先行还是LSB先行
	SPI_InitSturcture.SPI_Mode = SPI_Mode_Master;												// 设置为主SPI
	SPI_InitSturcture.SPI_NSS = SPI_NSS_Soft;														// 软件控制NSS(CS)引脚
	
	SPI_Init(DEBUG_SPIx, &SPI_InitSturcture);
	
	SPI_Cmd(DEBUG_SPIx, ENABLE);		// 使能SPI
}

void SPI_Config(void)
{
	SPI_GPIO_Config();
	
	SPI_MODE_Config();
}

// 发送一个字节
uint8_t SPI_Send_Byte(uint8_t data)
{
	uint16_t SPI_TIME_OUT = SPIT_FLAG_TIMEOUT;
	// 检查并等待至 TX缓冲区 为空
	while(SPI_I2S_GetFlagStatus(DEBUG_SPIx, SPI_I2S_FLAG_TXE) == RESET)
	{
		if((SPI_TIME_OUT--) == 0) return SPI_TIMEOUT_UserCallback(0);
	}
	
	// 程序执行到此处说明 TX缓冲区 已空
	SPI_I2S_SendData(DEBUG_SPIx, data);
	
	SPI_TIME_OUT = SPIT_FLAG_TIMEOUT;
	// 检查并等待至 RX缓冲区 为非空
	while(SPI_I2S_GetFlagStatus(DEBUG_SPIx, SPI_I2S_FLAG_RXNE) == RESET)
	{
		if((SPI_TIME_OUT--) == 0) return SPI_TIMEOUT_UserCallback(1);
	}
	/* 同步收发,一字节发送完毕说明一字节也接收完毕*/
	
	// 程序执行到此处说明 RX缓冲区 非空
	return SPI_I2S_ReceiveData(DEBUG_SPIx);
}

// 读取一个字节
uint8_t SPI_Read_Byte(void)
{
	/* flash会根据你写的命令来选择是否忽略后面发来的数据 */
	return SPI_Send_Byte(DUMMY); // 这里也可以发送0xff,flash会忽略这个数据
}

// 读取flahs id 
uint32_t SPI_Read_ID(void)
{
	/* 根据芯片手册,发送读取id指令,芯片会返回三个字节数据*/
	uint32_t flash_id;
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_JEDEC_ID);
	
	flash_id = SPI_Send_Byte(DUMMY);				// 接收数据
	
	flash_id <<= 8;
	
	flash_id |= SPI_Send_Byte(DUMMY);
	
	flash_id <<= 8;
	
	flash_id |= SPI_Send_Byte(DUMMY);
	
	SPI_CS_HIGH();
	
	return flash_id;
}

// 等待FLASH内部时序操作完成
void SPI_WaitForWriteEnd(void)
{
	uint8_t status_reg = 0;
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_STATUS);
	
	do
	{
		status_reg = SPI_Send_Byte(DUMMY);
	}while((status_reg & 0x01) == 1);
	
	
	SPI_CS_HIGH();
}

// FLASH写入使能
void SPI_Write_Enable(void)
{
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(WRITE_ENABLE);
	
	SPI_CS_HIGH();
}

// 擦除FLASH指定扇区
void SPI_Erase_Sector(uint32_t addr)
{
	SPI_Write_Enable(); // FLASH写入数据前要先发送写使能命令,擦除也算写操作
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(SECTOR_ERASE);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

// 读取FLASH的内容
void SPI_Read_Data(uint32_t addr, uint8_t *data, uint32_t numByteToRead)
{
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(READ_DATA);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	while(numByteToRead--)
	{
		*data = SPI_Send_Byte(DUMMY);
		data++;
	}
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

// 向FLASH写入内容,最多写入256字节
void SPI_Write_Data(uint32_t addr, uint8_t *writeBuff, uint32_t numByteToRead)
{
	SPI_Write_Enable();
	
	// 片选使能
	SPI_CS_LOW();
	
	SPI_Send_Byte(WRITE_DATA);
	
	SPI_Send_Byte((addr>>16)&0xff);				
	
	SPI_Send_Byte((addr>>8)&0xff);
	
	SPI_Send_Byte(addr&0xff);
	
	while(numByteToRead--)
	{
		SPI_Send_Byte(*writeBuff);
		writeBuff++;
	}
	
	SPI_CS_HIGH();
	
	SPI_WaitForWriteEnd();
}

void SPI_BufferWrite(uint32_t WriteAddr, uint8_t* pBuffer, uint16_t NumByteToWrite)
{
	uint8_t 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 刚好按页对齐*/
	if(Addr == 0)
	{
		/* NumByteToWrite < SPI_FLASH_PageSize */
		if(NumOfPage == 0)
		{
			SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
		}
		else	/* NumByteToWrite > SPI_FLASH_PageSize */
		{
			/* 先把整数页都写了 */
			while(NumOfPage--)
			{
				SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
				WriteAddr = WriteAddr + SPI_FLASH_PageSize;
				pBuffer = pBuffer + SPI_FLASH_PageSize;
			}
			/* 若有多余的不满一页的数据,先把它写完 */
			SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
		}
	}
	/* 若地址与SPI_FLASH_PageSize不对齐*/
	else
	{
		/* NumByteToWrite < SPI_FLASH_PageSize */
		if(NumOfPage == 0)
		{
			/* 当前页剩余的count个位置比NumOfSingle小,一页写不完 */
			if(NumOfSingle > count)
			{
				temp = NumOfSingle - count;
				/* 先写满当前页*/
				SPI_Write_Data(WriteAddr, pBuffer, count);
				
				WriteAddr = WriteAddr + count;
				pBuffer = pBuffer + count;
				/* 再写剩余的数据 */
				SPI_Write_Data(WriteAddr, pBuffer, temp);
			}
			else  /* 当前页剩余的count个位置能写完NumOfSingle个数据 */
			{
				SPI_Write_Data(WriteAddr, pBuffer, NumByteToWrite);
			}
		}
		else  /* NumByteToWrite > SPI_FLASH_PageSize */
		{
			/* 地址不对齐多出的count分开处理,不加入这个运算*/
			NumByteToWrite -= count;
			NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
			NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
			
			/* 先写完count个数据,为的是让下一次要写的地址对齐 */
			SPI_Write_Data(WriteAddr, pBuffer, count);
			
			/* 接下来就重复地址对齐的情况*/
			WriteAddr += count;
			pBuffer += count;
			
			/* 把整数页都写了 */
			while(NumOfPage--)
			{
				SPI_Write_Data(WriteAddr, pBuffer, SPI_FLASH_PageSize);
				WriteAddr = WriteAddr + SPI_FLASH_PageSize;
				pBuffer = pBuffer + SPI_FLASH_PageSize;
			}
			
			/* 若有对于的不满一页的数据,把它写完 */
			if(NumOfSingle != 0)
			{
				SPI_Write_Data(WriteAddr, pBuffer, NumOfSingle);
			}
		}
	}
}

/**
  * @brief  Basic management of the timeout situation.
  * @param  errorCode:错误代码,可以用来定位是哪个环节出错.
  * @retval 返回0,表示SPI读取失败.
  */
static  uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
  /* Block communication and all processes */
  EEPROM_ERROR("SPI 等待超时!errorCode = %d",errorCode);
  
  return 0;
}
// main.c
#include "stm32f10x.h"
#include "bsp_spi.h"
#include "usart.h"

uint8_t readBuff[4096];
uint8_t writeBuff[4096];

int main(void)
{
	uint32_t flash_id;
	
	int i;
	
	USART_Config();
	
	printf("\r\n TEST \r\n");
	
	SPI_Config();
	
	flash_id = SPI_Read_ID();
	
	printf("\r\n flash_id = 0x%x \r\n", flash_id);
	
	SPI_Erase_Sector(0);
	
	for(i = 0; i < 30; i++)
	{
		writeBuff[i] = i;
	}
	
	SPI_Write_Data(0, writeBuff, 30);
	
	SPI_Read_Data(0, readBuff, sizeof(readBuff));
	
	for(i = 0; i < 4096; i++)
	{
		printf("0x%x ", readBuff[i]);
		if(i%10 == 0)
			printf("\r\n");
	}
	
	while(1)
	{
	
	}
}

堆栈问题一般会跳到HardFault_Handler函数中,程序跑到这里多半是因为内存错误。

4.3 软件SPI

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
stm32--tsl256.zip_stm32是一个压缩文件,其中包含了与stm32和tsl256芯片相关的文件和代码。stm32是一种32位的单片机系列,由STMicroelectronics公司推出。它具有高性能、低功耗和丰富的外设接口,适用于各种嵌入式系统应用。tsl256是一种环境光传感器芯片,用于检测周围环境的光照强度。 这个压缩文件可以包含stm32芯片和tsl256传感器之间的通信协议、引脚连接方式、初始化代码以及一些示例应用程序。通过使用这些文件和代码,开发人员可以很方便地在stm32上使用tsl256传感器来测量光照强度。例如,可以通过读取tsl256传感器输出的模拟电压值,转换为相应的光照强度数值。开发人员可以根据需要进行修改和定制,以适应具体的应用场景。 使用stm32--tsl256.zip_stm32文件,开发人员可以节省大量的开发时间和精力。他们无需从零开始编写通信协议和初始化代码,也无需从头研究如何与tsl256传感器进行交互。相反,他们可以直接使用这些提供的文件和代码,快速实现与tsl256传感器的集成,并进行光照强度的测量和应用开发。 总之,stm32--tsl256.zip_stm32是一个方便开发人员在stm32上使用tsl256传感器的压缩文件。它包含了相关的文件和代码,可以用于通信协议、引脚连接、初始化以及示例应用程序。通过使用这些文件和代码,开发人员可以快速、方便地实现在stm32上使用tsl256传感器进行光照强度测量和应用开发。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值