STM32—SPI详解入门(使用SPI通讯读写W25Q128模块)

这篇具有很好参考价值的文章主要介绍了STM32—SPI详解入门(使用SPI通讯读写W25Q128模块)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、SPI是什么

二、SPI物理架构

三、SPI工作原理

四、SPI工作模式

五、SPI相关寄存器介绍

六、SPI用到的结构体与函数

1.结构体

2.函数

七、W25Q128芯片

1.W25Q128介绍

2.W25Q128存储架构

3.W25Q128常用指令

4.W25Q128状态寄存器

5.W25Q128常见操作流程

八、实验(使用SPI通讯读写W25Q128模块)

1.接线

2.配置

3.代码

1.main.c文件

2.w25q128.c文件(向工程添加w25q128.c文件)

3.w25q128.h文件(向工程添加w25q128.h文件)

4.spi.c文件编写

5.spi.h文件编写

九、STM32工程添加.c和.h文件


一、SPI是什么

        SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 AT91RM9200 。

二、SPI物理架构

        SPI总线包含四条总线:分别为MOSI、MISO、SCK、NSS(CS)。

(1)MISO:主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

(2)MOSI:主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

(3)SCK:时钟信号,可以使主从设备同步输入输出。

(4)NSS(CS): 由主设备控制,用来选择指定的从设备进行通信。(当主设备想要读/写从设备时,首先拉低从设备对应的NSS线)。

三、SPI工作原理

        1.SPI主从模式

         SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,主设备通过从设备各自的片选信号(NSS)来选择从设备。

        2.SPI主、从设备通讯接线

                一个主设备和一个从设备

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

                 一个主设备和多个从设备        

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        3.SPI数据传输

        SPI主设备和从设备都有一个移位寄存器,主机可以通过向它的移位寄存器写入数据来发起一次SPI通讯,主设备的7移到从设备的0上,而从设备的7移到主设备的0上。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

1.主设备拉低对应从设备的NSS信号线。(选择从设备进行通信)

2.主设备发送时钟信号,从设备接收时钟信号。(告诉从设备开始进行SPI通讯)

3.数据交换

        主设备(Master)将要发送的数据传输到发送缓存区(Menory),当从设备收到主设备发送的时钟信号,并且在MOSI引脚上出现第一个数据位时,发送过程开始。余下的位被装进移位寄存器,通过MOSI信号线将字节一位一位的发送给从设备。同时主设备通过MISO引脚将数据一位一位的接收到移位寄存器,当数据接收完成时,将数据传输到接收缓冲区。

        从设备同理,将自己发送缓冲区的数据通过移位寄存器和MISO一位一位发送给主设备,同时通过MOSI引脚将数据一位一位的接收到移位寄存器,当数据接收完成时,将数据传输到接收缓冲区 

        SPI只有主模式和从模式之分,没有读和写的说法,数据的写操作和读操作是同步完成的。

                      i.如果只进行写操作,主机只需忽略接收到的字节。           

                      ii.如果只进行读操作,只需发送一个空字节来获取SPI通讯的一个字节。

四、SPI工作模式

1.时钟极性(CPOL)

        控制在没有数据传输时时钟线的空闲状态电平。       

  • 0:SCK在空闲状态保持低电平。

  • 1:SCK在空闲状态保持高电平。

2.时钟相位(CPHA)

        时钟线在第几个时钟边沿采样数据。

  • 0:SCK的第一个(奇数)边沿进行数据位采样,数据在第一个时钟边沿被锁存。

  • 1:SCK的第二个(偶数)边沿进行数据位采样,数据在第二个时钟边沿被锁存。

3.SPI模式时序图

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        模式0(常用)(CPOL = 0,CPHA = 0)

             空闲时SCK时钟为低电平,采样时刻为第一个边沿即上升沿。如图所示,黄线进行采样

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         模式1(CPOL = 0,CPHA = 1)

             空闲时SCK时钟为低电平,采样时刻为第二个边沿即下降沿。如图所示,黄线进行采样。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         模式2(CPOL = 1,CPHA = 0)

             空闲时SCK时钟为高电平,采样时刻为第一个边沿即下降沿。如图所示,黄线进行采样。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        模式3(常用)(CPOL = 1,CPHA = 1)

            空闲时SCK时钟为高电平,采样时刻为第二个边沿即上升沿。如图所示,黄线进行采样。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

五、SPI相关寄存器介绍

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

六、SPI用到的结构体与函数

1.结构体

(句柄结构体)SPI_HandleTypeDef

typedef struct __SPI_HandleTypeDef
{
  SPI_TypeDef  *Instance;  /* SPIx */
 
  SPI_InitTypeDef  Init;  /* SPI初始化结构体:通信参数 */
 
} SPI_HandleTypeDef;

(初始化结构体)SPI_InitTypeDefSPI

typedef struct
{
  uint32_t Mode;  /* SPI模式(主机模式。从机模式) */
 
  uint32_t Direction;  /* 工作方式(全双工方式、半双工、只读、只写) */
 
  uint32_t DataSize;  /* 数据格式(8bit、16bit) */
 
  uint32_t CLKPolarity;  /* 时钟极性(CPOL) */
 
  uint32_t CLKPhase;  /* 时钟相位(CPHA) */
 
  uint32_t NSS;  /* SS控制方式(软件) */
 
  uint32_t BaudRatePrescaler;  /* SPI波特率预分频值 */
 
  uint32_t FirstBit;  /* 数据传输顺序(MSB、LSB) */
 
  uint32_t TIMode;  /* 数据帧格式(Motorola、TI)*/
 
  uint32_t CRCCalculation;  /* 设置硬件CRC检验 */
 
  uint32_t CRCPolynomial;  /* 设置CRC检验多项式 */
 
} SPI_InitTypeDef;

2.函数

__HAL_RCC_SPI1_CLK_ENABLE()

使能SPI时钟。(使用STM32CubeMX会自动配置)

HAL_SPI_Init()

初始化SPI。(使用STM32CubeMX会自动配置)

HAL_SPI_MspInit()

初始化SPI相关引脚。(使用STM32CubeMX会自动配置)

HAL_SPI_Transmit()  (SPI发送数据)

原型:
HAL_StatusTypeDef HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

参数:
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pData:发送数据的存储地址
uint16_t Size:发送的数据量大小
uint32_t Timeout:超时时间

实例:
uint8_t uint8_t data = 56;

HAL_SPI_TransmitReceive(&hspi1, &data, 1, 1000);

HAL_SPI_Receive()  (SPI接收数据)

原型:
HAL_StatusTypeDef HAL_SPI_Receive(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)

参数:
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pData:接收数据的存储地址
uint16_t Size:接收的数据量大小
uint32_t Timeout:超时时间

实例:
uint8_t uint8_t data;

HAL_SPI_TransmitReceive(&hspi1, &data, 1, 1000);

HAL_SPI_TransmitReceive()  (SPI发送接收数据)

原型:
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,uint32_t Timeout)

参数:
SPI_HandleTypeDef *hspi:SPI句柄
uint8_t *pTxData:发送数据的存储地址
uint8_t *pRxData:接收数据的存储地址
uint16_t Size:发送和接收的数据量大小
uint32_t Timeout:超时时间

实例:
uint8_t spi1_read_write_byte(uint8_t data)
{
uint8_t rec_data = 0;

HAL_SPI_TransmitReceive(&hspi1, &data, &rec_data, 1, 1000);

return rec_data;
}

__HAL_SPI_ENABLE()   (使能SPI外设)

__HAL_SPI_DISABLE()    (失能SPI外设)

七、W25Q128芯片

1.W25Q128介绍

  • W25Q128是华邦公司推出的一款SPI接口的NOR FIash芯片,其存储空间为128 Mbit,相当于16M字节。

  • Flash 是常用的用于储存数据的半导体器件,它具有容量大,可重复擦写、按”扇区/块”擦除、掉电后数据可继续保存的特性。

  • Flash 是有一个物理特性:只能写0,不能写1,靠擦除来写1。

  • 支持SPI模式1。

  • 数据格式:8bit,MSB。

2.W25Q128存储架构

  • 一个W25Q128 = 256个块 = 256 * 16个扇区 = 256 * 16 *16个页 = 256 * 16 * 16 * 256个字节,即16777216字节,约16M字节,即寻址范围为0x00 ~ 0xFFFFFF。

  • 16777216 -1 = 0xFFFFFF。

  • 对Flash擦除时一般按扇区(4K = 4096字节)来进行擦除。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

3.W25Q128常用指令

        W25Q128 全部指令非常多,但常用的如下几个指令:

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

1.写使能(0x06)

  • 写使能指令将状态寄存器中的WEL位设置为1。

  • 必须在每个页写、扇区擦除、块擦除、芯片擦除和写状态寄存器指令之前进行写使能。

  • 操作:拉低CS片选->发送指令0x06 ->拉高CS片选。

2.读SR1(0x05)

  • 读取状态寄存器指令允许读取8位状态寄存器的值。

  • 操作:拉低CS片选 ->发送指令0x05 ->定义一个uint8_t数据接收SR1的返回值 ->拉高CS片选 

3.读数据(0x03)

  • 读取数据指令允许从存储器顺序读取一个或多个数据字节。

  • 操作:拉低CS片选 -> 发送指令0x03 -> 发送24位地址 -> 读取数据 -> 拉高CS片选

4.页写(0x02)

  • 页写指令允许在指定地址写入小于256字节的指定长度的数据,在非0xFF处写入的数据会失败。

  • 操作:写使能 -> 拉低CS片选 -> 发送指令0x02 -> 发送24位地址 -> 写入数据 -> 拉高CS片选 -> 等待写入结束(即判断状态寄存器的BUSY位是否置0

5.扇区擦除时序(0x20)

        写入数据前,检查内存空间是否全部都是 0XFF ,不满足需擦除。

        拉低CS片选 → 发送20H→ 发送24位地址 → 拉高CS片选

6.芯片擦除(0xC7)

  • 芯片擦除指令将W25Q128的所有数据都擦除为0xFF。

  • 操作:写使能 -> 等待空闲(即判断状态寄存器的BUSY位是否置0) -> 拉低CS片选 -> 发送指令0xC7 -> 拉高CS片选 -> 等待芯片擦除完成(即判断状态寄存器的BUSY位是否置0)

7.读取W25Q128的芯片ID(0x90)

  • 读取制造商/设备ID指令。

  • 操作:拉低片选信号 -> 发送24位地址,地址为0xFFFFFF -> 定义一个uint16_t数据接收芯片ID -> 拉高片选信号

4.W25Q128状态寄存器

        W25Q128一共有3个状态寄存器,它们的作用时跟踪芯片的状态。其中,状态寄存器1比较常用。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         1.BUSY位(指示当前的状态)        

        BUSY是状态寄存器中的只读位,当设备执行页程序、四页程序、扇区擦除、块擦除、芯片擦除、写状态寄存器或擦除/程序安全寄存器指令时,将其设置为1状态。 在此期间,器件将忽略除读取状态寄存器和擦除/程序挂起指令之外的其他指令。 当编程、擦除或写入状态/安全寄存器指令完成时,忙位将被清除为0状态,表示设备已准备好接受进一步的指令。

0:空闲

1:忙
        2.WEL位(写使能锁定)

        WEL是状态寄存器(S1)中的只读位,在执行写使能指令后被设置为1。 当设备被禁止写入时,WEL状态位被清除为0。 在上电时或在下列任何指令之后发生写禁用状态:写禁用、页程序、四页程序、扇区擦除、块擦除、芯片擦除、写状态寄存器、擦除安全寄存器和程序安全寄存器。

1:可以操作页、扇区、块

0:禁止写入
 

5.W25Q128常见操作流程

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

写操作:

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

八、实验(使用SPI通讯读写W25Q128模块)

1.接线

        W25Q128与STM32F103C8T6板子接线,在STM32F103C8T6的产品手册中找到板子上的SPI1的接口。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        PA4作为SPI1的NSS,PA5作为SPI1的CLK,PA6作为SPI1的DO(MISO),PA7作为SPI1的DI(MOSI)。

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

2.配置

        1.SYS

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        2. RCC

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

 stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         3.SPI1

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         4.SUART1

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

          5.使用MicroLIB库

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

3.代码

1.main.c文件

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"
 
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
 
#include "stdio.h"
#include "string.h"
#include "w25q128.h"
 
/* USER CODE END Includes */
 
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
 
/* USER CODE END PTD */
 
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
 
#define TEXT_SIZE 16
#define  FLASH_WriteAddress     0x000000  //数据写入w25q128的地址,地址范围为0x000000 ~ 0xFFFFFF
#define  FLASH_ReadAddress      FLASH_WriteAddress
 
/* USER CODE END PD */
 
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
 
/* USER CODE END PM */
 
/* Private variables ---------------------------------------------------------*/
 
/* USER CODE BEGIN PV */
 
/* USER CODE END PV */
 
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
 
/* USER CODE END PFP */
 
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
 
 
//重写stdio.h文件中的prinft()里的fputc()函数
int fputc(int my_data,FILE *p)
{
    unsigned char temp = my_data;
    //改写后,使用printf()函数会将数据通过串口一发送出去
    HAL_UART_Transmit(&huart1,&temp,1,0xffff);  //0xfffff为最大超时时间
    return my_data;
}
 
 
/* USER CODE END 0 */
 
/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{
  /* USER CODE BEGIN 1 */
 
    uint8_t datatemp[TEXT_SIZE];
    
  /* 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_USART1_UART_Init();
  MX_SPI1_Init();
  /* USER CODE BEGIN 2 */
 
    /* w25q128初始化 */
    w25q128_init();
    
    /* 写入测试数据 */
    sprintf((char *)datatemp, "hello jiangxiao");
    w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
    printf("数据写入完成!\r\n");
    
    /* 读出测试数据 */
    memset(datatemp, 0, TEXT_SIZE);
    w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
    printf("读出数据:%s\r\n", datatemp);
 
  /* USER CODE END 2 */
 
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */
 
    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}
 
/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
 
  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
 
  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
 
  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}
 
/* USER CODE BEGIN 4 */
 
/* USER CODE END 4 */
 
/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}
 
#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

2.w25q128.c文件(向工程添加w25q128.c文件)

#include "w25q128.h"
#include "spi.h"
#include "stdio.h"
 
 
 
//w25q128初始化
void w25q128_init(void)
{
		uint16_t flash_type;
    spi1_read_write_byte(0xFF); /* 清除DR(数据寄存器),写入一个0xFF */
    W25Q128_CS(1);  //拉高片选信号不进行SPI通信
		flash_type = w25q128_read_id();   /* 读取FLASH ID. */
		if (flash_type == W25Q128){
			printf("检测到W25Q128芯片\r\n");
		}
}
 
 
 
//等待W25Q128空闲
static void w25q128_wait_busy(void)
{
    while ((w25q128_rd_sr1() & 0x01) == 1);   /* 等待状态寄存器的BUSY位清空 */
}
 
 
 
//读取状态寄存器的值
uint8_t w25q128_rd_sr1(void)
{
    uint8_t rec_data = 0;
    
    W25Q128_CS(0);
		spi1_read_write_byte(FLASH_ReadStatusReg1);     // 写入指令0x05:读状态寄存器1
    rec_data = spi1_read_write_byte(0xFF);  //获取状态寄存器1的值
    W25Q128_CS(1);
    
    return rec_data;
}
 
 
 
//W25Q128写使能,即置位WEL为1
void w25q128_write_enable(void)
{
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_WriteEnable);   /* 发送指令0x06:写使能 */
    W25Q128_CS(1);
}
 
 
 
//发送24位地址
static void w25q128_send_address(uint32_t address)  /*address:地址范围0~16777215字节,即寻址范围为0x00 ~ 0xFFFFFF */
{
    spi1_read_write_byte((uint8_t)((address)>>16));     /* 发送 bit23 ~ bit16 地址 */
    spi1_read_write_byte((uint8_t)((address)>>8));      /* 发送 bit15 ~ bit8  地址 */
    spi1_read_write_byte((uint8_t)address);             /* 发送 bit7  ~ bit0  地址 */
}
 
 
 
//擦除整个芯片
void w25q128_erase_chip(void)
{
    w25q128_write_enable();    /* 写使能 */
    w25q128_wait_busy();       /* 等待空闲 */
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ChipErase);  /* 发送指令0xC7:擦除整个芯片 */ 
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待芯片擦除结束 */
}
 
 
 
//擦除一个扇区
void w25q128_erase_sector(uint32_t saddr)  /* saddr:该参数是第几个扇区 */
{
    saddr *= 4096;  /* 一个扇区大小为4096字节 */
    w25q128_write_enable();        /* 写使能 */
    w25q128_wait_busy();           /* 等待空闲 */
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_SectorErase);    /* 发送指令0x20:擦除指定扇区 */
    w25q128_send_address(saddr);   /* 发送擦除的扇区地址 */
    W25Q128_CS(1);
    w25q128_wait_busy();           /* 等待扇区擦除完成 */
}
 
 
 
//读取w25q128芯片ID
uint16_t w25q128_read_id(void)
{
    uint16_t deviceid;
 
    W25Q128_CS(0);  //拉低片选信号进行SPI通信
    spi1_read_write_byte(FLASH_ManufactDeviceID);   /* 发送读取 ID 命令 */
		/* 发送3个0 */
		/*
    spi1_read_write_byte(0);    
    spi1_read_write_byte(0);
    spi1_read_write_byte(0);
		*/
	
		w25q128_send_address(0x000000);
	
    deviceid = spi1_read_write_byte(0xFF) << 8;     /* 读取高8位字节 */
    deviceid |= spi1_read_write_byte(0xFF);         /* 读取低8位字节 */
    W25Q128_CS(1);
 
    return deviceid;
}
 
/*
读取W25Q128的FLASH,在指定地址开始读取指定长度的数据
pubf:需要读取的数据
addr:指定的地址
datalen:指定的数据大小
*/
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;
 
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_ReadData);       /* 发送指令0x03:读取数据 */
    w25q128_send_address(addr);                /* 发送需要读取的数据地址 */
    
    for(i=0;i<datalen;i++)
    {
        pbuf[i] = spi1_read_write_byte(0XFF);   /* 循环读取 */
    }
    
    W25Q128_CS(1);
}
 
 
 
/*
单页写,在指定地址写入小于256字节的指定长度的数据,在非0xFF处写入的数据会失败
pubf:需要写入的数据
addr:指定的地址
datalen:指定的数据大小
*/
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t i;
 
    w25q128_write_enable();    /* 写使能 */
 
    W25Q128_CS(0);
    spi1_read_write_byte(FLASH_PageProgram);    /* 发送命令0x02:页写 */
    w25q128_send_address(addr);                /* 发送写入的页地址 */
 
    for(i=0;i<datalen;i++)
    {
        spi1_read_write_byte(pbuf[i]);          /* 循环写入 */
    }
    
    W25Q128_CS(1);
    w25q128_wait_busy();       /* 等待写入结束 */
}
 
 
 
/*
多页写,在指定地址写入指定长度的数据,在非0xFF处写入的数据会失败
pubf:需要写入的数据
addr:指定的地址
datalen:指定的数据大小
*/
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint16_t pageremain;
    pageremain = 256 - addr % 256;  /* 获取指定地址那页的剩余字节数 */
 
    if (datalen <= pageremain)      /* 指定地址那页的剩余字节数能装下指定数据大小 */
    {
        pageremain = datalen;  
    }
 
    while (1)
    { 
			/* 当指定地址那页的剩余字节数能装下指定数据大小时,一次性写完 */
            
			/* 当指定数据大小比指定地址那页的剩余字节数要大时, 先写完指定地址那页的剩余字节, 然后根据剩余数据大小进行不同处理 */
        w25q128_write_page(pbuf, addr, pageremain);  //页写
 
        if (datalen == pageremain)   /* 写入结束了 */
        {
            break;  
        }
        else     /* datalen > pageremain */
        {
            pbuf += pageremain;         /* pbuf指针地址偏移,前面已经写了pageremain字节 */
            addr += pageremain;         /* 写地址偏移,前面已经写了pageremain字节 */
            datalen -= pageremain;      /* 写入总长度减去已经写入了的字节数 */
 
            if (datalen > 256)          /* 剩余数据大小还大于一页 */
            {
                pageremain= 256;       /* 一次写入256个字节,即一次写一页 */
            }
            else     /* 剩余数据大小小于一页  */
            {
                pageremain= datalen;   /* 一次性写完 */
            }
        }
    }
}
 
 
/*
//写入W25Q128的FLASH,在指定地址开写入取指定长度的数据
pubf:需要写入的数据
addr:指定的地址
datalen:指定的数据大小
*/
uint8_t g_w25q128_buf[4096];   /* 扇区缓存 */
 
void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *w25q128_buf;
 
    w25q128_buf = g_w25q128_buf;
    secpos = addr / 4096;       /* 获取指定地址在哪片扇区 */
    secoff = addr % 4096;       /* 指定数据在在扇区内的偏移数据大小 */
    secremain = 4096 - secoff;  /* 扇区剩余字节数 */
 
    if (datalen <= secremain)  /* 指定地址那片扇区的剩余字节数能装下指定数据大小 */
    {
        secremain = datalen;    
    }
 
    while (1)
    {
        w25q128_read(w25q128_buf, secpos * 4096, 4096);   /* 读出指定地址那片扇区的全部内容 */
 
        for (i = 0; i < secremain; i++)   /* 校验数据,防止数据出现非0xFF */
        {
            if (w25q128_buf[secoff + i] != 0xFF)  //扇区数据有一个数据不是0xFF
            {
                break;      /* 需要擦除, 直接退出for循环 */
            }
        }
 
        if (i < secremain)   /* 需要擦除 */
        {
            w25q128_erase_sector(secpos);  /* 擦除这个扇区 */
 
            for (i = 0; i < secremain; i++)   /* 复制 */
            {
                w25q128_buf[i + secoff] = pbuf[i];
            }
 
            w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        }
        else        /* 写已经擦除了的,直接写入扇区剩余区间. */
        {
            w25q128_write_nocheck(pbuf, addr, secremain);  /* 直接写扇区 */
        }
 
        if (datalen == secremain)
        {
            break;  /* 写入结束了 */
        }
        else        /* 写入未结束 */
        {
            secpos++;               /* 扇区地址增1,新的一个扇区 */
            secoff = 0;             /* 偏移位置为0 */
 
            pbuf += secremain;      /* 指针偏移 */
            addr += secremain;      /* 写地址偏移 */
            datalen -= secremain;   /* 字节数递减 */
 
            if (datalen > 4096)
            {
                secremain = 4096;   /* 一次写入一个扇区 */
            }
            else
            {
                secremain = datalen;/* 一次性写完 */
            }
        }
    }
}
 

3.w25q128.h文件(向工程添加w25q128.h文件)

#include "stdint.h"
 
/* W25Q128片选引脚定义 */
#define W25Q128_CS_GPIO_PORT           GPIOA
#define W25Q128_CS_GPIO_PIN            GPIO_PIN_4
 
/* W25Q128片选信号 */
#define W25Q128_CS(x)      do{ x ? \
                                  HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_SET) : \
                                  HAL_GPIO_WritePin(W25Q128_CS_GPIO_PORT, W25Q128_CS_GPIO_PIN, GPIO_PIN_RESET); \
                            }while(0)
 
/* FLASH芯片列表 */
#define W25Q128     0XEF17          /* W25Q128  芯片ID */
 
/* 指令表 */
#define FLASH_WriteEnable                        0x06 
#define FLASH_ReadStatusReg1                0x05 
#define FLASH_ReadData                            0x03 
#define FLASH_PageProgram                        0x02 
#define FLASH_SectorErase                        0x20 
#define FLASH_ChipErase                            0xC7 
#define FLASH_ManufactDeviceID            0x90 
 
/* 静态函数 */
static void w25q128_wait_busy(void);                                                                                                //等待W25Q128空闲       
static void w25q128_send_address(uint32_t address);                                                                      //发送24位地址
static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen);         //单页写,在指定地址写入小于256字节的指定长度的数据,在非0xFF处写入的数据会失败
static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen);        //多页写,在指定地址写入指定长度的数据,在非0xFF处写入的数据会失败
 
/* 普通函数 */
void w25q128_init(void);                            //w25q128初始化  
uint16_t w25q128_read_id(void);       //读取w25q128芯片ID   
void w25q128_write_enable(void);      //W25Q128写使能,即置位WEL为1    
uint8_t w25q128_rd_sr1(void);                 //读取状态寄存器的值        
 
void w25q128_erase_chip(void);               //擦除整个芯片      
void w25q128_erase_sector(uint32_t saddr);   //擦除一个扇区
void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen);     //读取W25Q128的FLASH,在指定地址开始读取指定长度的数据
void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen);    //写入W25Q128的FLASH,在指定地址开写入取指定长度的数据
 
 
 
 

4.spi.c文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    spi.c
  * @brief   This file provides code for the configuration
  *          of the SPI instances.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "spi.h"
 
/* USER CODE BEGIN 0 */
 
/* USER CODE END 0 */
 
SPI_HandleTypeDef hspi1;  /* SPI句柄 */
 
/* SPI1 init function */
void MX_SPI1_Init(void)
{
 
  /* USER CODE BEGIN SPI1_Init 0 */
 
  /* USER CODE END SPI1_Init 0 */
 
  /* USER CODE BEGIN SPI1_Init 1 */
 
  /* USER CODE END SPI1_Init 1 */
  hspi1.Instance = SPI1;
  hspi1.Init.Mode = SPI_MODE_MASTER;  /* 设置SPI模式主机模式 */
  hspi1.Init.Direction = SPI_DIRECTION_2LINES;  /* 设置SPI工作方式:全双工 */
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;  /* 设置数据格式:8bit */
    
    /* SPI模式1 */
  hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;  /* 设置时钟极性:CPOL = 0 */
  hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;  /* 设置时钟相位:CPHA = 1 */
  
    hspi1.Init.NSS = SPI_NSS_SOFT;  /* 设置片选方式:软件NSS */
  hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;  /* 设置SPI时钟波特率分频:256分频 */
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;  /* 设置数据大小端:MSB */
  hspi1.Init.TIMode = SPI_TIMODE_DISABLE;  /* 设置数据帧格式:Motolora */
  hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;  /* 设置CRC校验:关闭CRC检验 */
  hspi1.Init.CRCPolynomial = 10;  /* 设置CRC校验多项式:1~65535 */
  if (HAL_SPI_Init(&hspi1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN SPI1_Init 2 */
 
  /* USER CODE END SPI1_Init 2 */
 
}
 
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
 
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspInit 0 */
 
  /* USER CODE END SPI1_MspInit 0 */
    /* SPI1 clock enable */
    __HAL_RCC_SPI1_CLK_ENABLE();
 
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**SPI1 GPIO Configuration
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI
    */
    GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
 
  /* USER CODE BEGIN SPI1_MspInit 1 */
 
  /* USER CODE END SPI1_MspInit 1 */
  }
}
 
void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
{
 
  if(spiHandle->Instance==SPI1)
  {
  /* USER CODE BEGIN SPI1_MspDeInit 0 */
 
  /* USER CODE END SPI1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_SPI1_CLK_DISABLE();
 
    /**SPI1 GPIO Configuration
    PA5     ------> SPI1_SCK
    PA6     ------> SPI1_MISO
    PA7     ------> SPI1_MOSI
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
 
  /* USER CODE BEGIN SPI1_MspDeInit 1 */
 
  /* USER CODE END SPI1_MspDeInit 1 */
  }
}
 
/* USER CODE BEGIN 1 */
 
/*通过SPI1同时读写一个字节数据
主机只向从机进行写操作,调用此函数时忽略返回值
主机只向从机进行读操作,调用此函数时随便传入一个字符,尽量是0xFF
*/
uint8_t spi1_read_write_byte(uint8_t data)  
{
    uint8_t rec_data = 0;
    
    HAL_SPI_TransmitReceive(&hspi1, &data, &rec_data, 1, 1000);  //spi读写数据函数,参数2存放用来发送的数据,参数3存放用来接收的数据
   
    return rec_data;  
}
 
/* USER CODE END 1 */

5.spi.h文件编写

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file    spi.h
  * @brief   This file contains all the function prototypes for
  *          the spi.c file
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2023 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __SPI_H__
#define __SPI_H__
 
#ifdef __cplusplus
extern "C" {
#endif
 
/* Includes ------------------------------------------------------------------*/
#include "main.h"
 
/* USER CODE BEGIN Includes */
 
/* USER CODE END Includes */
 
extern SPI_HandleTypeDef hspi1;
 
/* USER CODE BEGIN Private defines */
 
/* USER CODE END Private defines */
 
void MX_SPI1_Init(void);
 
/* USER CODE BEGIN Prototypes */
 
uint8_t spi1_read_write_byte(uint8_t data);
 
/* USER CODE END Prototypes */
 
#ifdef __cplusplus
}
#endif
 
#endif /* __SPI_H__ */
 

九、STM32工程添加.c和.h文件

        1.在创建好的STM32工程中找到Core的文件夹

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

         2.向文件夹里添加新的xxx.c文件或xxx.h文件

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        3.在keil5中导入工程后,将这两个文件添加到工程列表中

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

 stm32 w25q128,STM32,stm32,单片机,嵌入式硬件

        4.添加头文件

        只需在需要添加头文件的c文件中写上需添加的头文件再编译就能自动添加

        例如下图

stm32 w25q128,STM32,stm32,单片机,嵌入式硬件 文章来源地址https://www.toymoban.com/news/detail-628647.html

到了这里,关于STM32—SPI详解入门(使用SPI通讯读写W25Q128模块)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • STM32CubeMX教程26 FatFs 文件系统 - W25Q128读写

    正点原子stm32f407探索者开发板V2.4 STM32CubeMX软件(Version 6.10.0) keil µVision5 IDE(MDK-Arm) ST-LINK/V2驱动 野火DAP仿真器 XCOM V2.6串口助手 使用STM32CubeMX软件配置STM32F407开发板 使用FatFs中间件通过SPI通信协议对W25Q128芯片进行读写等操作 关于STM32F407使用SPI通信协议对W25Q128 FLASH芯片读写

    2024年02月19日
    浏览(42)
  • 【STM32】SPI初步使用 读写FLASH W25Q64

    (1) SS( Slave Select):从设备选择信号线,常称为片选信号线,每个从设备都有独立的这一条 NSS 信号线,当主机要选择从设备时,把该从设备的 NSS 信号线设置为低电平,该从设备即被选中,即片选有效,接着主机开始与被选中的从设备进行 SPI通讯。所以 SPI通讯以 NSS 线置低电

    2024年02月10日
    浏览(42)
  • STM32-SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片)

    ​  SPI(Serial Peripheral Interface)是由Motorola公司开发的一种通用数据总线四根通信线:SCK(Serial Clock)、MOSI(Master Output Slave Input)、MISO(Master Input Slave Output)、SS(Slave Select)。 ​SPI通信具有以下特点: 同步,全双工; 支持总线挂载多设备(SPI仅支持一主多从); 在不

    2024年02月08日
    浏览(39)
  • stm32(SPI读写W25Q18)

    SPI是串行外设接口(Serial Peripheral Interface)的缩写,是一种 高速的,全双工,同步 的通信总 线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提 供方便,正是出于这种简单易用的特性,越来越多的芯片集成了这种通信协议,比如 A

    2024年02月16日
    浏览(48)
  • 【STM32】STM32学习笔记-硬件SPI读写W25Q64(40)

    在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I2S音频协议。SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式。 在小容量和中容量产品上,不支持I2S音频协议。 串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方式

    2024年02月19日
    浏览(60)
  • 【STM32】STM32学习笔记-软件SPI读写W25Q64(38)

    在大容量产品和互联型产品上,SPI接口可以配置为支持SPI协议或者支持I 2 S音频协议。SPI接口默认工作在SPI方式,可以通过软件把功能从SPI模式切换到I2S模式。 在小容量和中容量产品上,不支持I 2 S音频协议。 串行外设接口(SPI)允许芯片与外部设备以半/全双工、同步、串行方

    2024年01月24日
    浏览(41)
  • 【STM32】软件SPI读写W25Q64芯片

    目录 W25Q64模块 W25Q64芯片简介 硬件电路 W25Q64框图 Flash操作注意事项 状态寄存器 ​编辑 指令集 INSTRUCTIONS​编辑 ​编辑 SPI读写W25Q64代码 硬件接线图 MySPI.c MySPI.h W25Q64 W25Q64.c W25Q64.h W25Q64_Ins.h main.c 测试 SPI通信(W25Q64芯片简介,使用SPI读写W25Q64存储器芯片)  SPI通信文章:【

    2024年02月19日
    浏览(39)
  • 2023版 STM32实战11 SPI总线读写W25Q

    英文全称:Serial peripheral Interface 串行外设接口 -1- 串行(逐bit传输) -2- 同步(共用时钟线) -3- 全双工(收发可同时进行) -4- 通信只能由主机发起(一主,多从机) -1- CS片选一般配置为软件控制 -2- 片选低电平有效,从器件CS引脚可直接连接GND -3- 从机不能主动给主机发数据 -4- 主机想要

    2024年02月08日
    浏览(29)
  • 【STM32CubeMX学习】SPI读写W25Q16

            SPI分为主从工作模式,通常有一个主设备和一个或多个从设备,本文中MCU为主机,W25Q16为从机。 SPI通信有以下四根线: MISO:主设备数据输入,从设备数据输出。 MOSI:主设备数据输出,从设备数据输入。 SCLK:时钟信号,由主设备产生。 CS:从设备片选信号,由

    2024年02月03日
    浏览(40)
  • 基于STM32实现W25Q16读写操作(spi)

    在之前我们学习了flash闪存,这个更多的是内部数据存储,容量也是会比较小。这次我们来学习一下更多的存储单元w25q16,顺便了解spi———串行外围设备接口。 在我们的核心板子上基本都会有这么一块芯片,只是有的容量会计较大,大家可以查看板子的原理图,如图所示:

    2024年01月19日
    浏览(31)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包