当前位置:   article > 正文

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

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主、从设备通讯接线

                一个主设备和一个从设备

                 一个主设备和多个从设备        

        3.SPI数据传输

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

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模式时序图

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

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

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

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

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

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

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

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

五、SPI相关寄存器介绍

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

1.结构体

(句柄结构体)SPI_HandleTypeDef

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

(初始化结构体)SPI_InitTypeDefSPI

  1. typedef struct
  2. {
  3. uint32_t Mode; /* SPI模式(主机模式。从机模式) */
  4. uint32_t Direction; /* 工作方式(全双工方式、半双工、只读、只写) */
  5. uint32_t DataSize; /* 数据格式(8bit、16bit) */
  6. uint32_t CLKPolarity; /* 时钟极性(CPOL) */
  7. uint32_t CLKPhase; /* 时钟相位(CPHA) */
  8. uint32_t NSS; /* SS控制方式(软件) */
  9. uint32_t BaudRatePrescaler; /* SPI波特率预分频值 */
  10. uint32_t FirstBit; /* 数据传输顺序(MSB、LSB) */
  11. uint32_t TIMode; /* 数据帧格式(Motorola、TI)*/
  12. uint32_t CRCCalculation; /* 设置硬件CRC检验 */
  13. uint32_t CRCPolynomial; /* 设置CRC检验多项式 */
  14. } 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字节)来进行擦除。

3.W25Q128常用指令

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

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比较常用。

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

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

0:空闲

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

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

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

0:禁止写入
 

5.W25Q128常见操作流程

写操作:

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

1.接线

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

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

2.配置

        1.SYS

        2. RCC

 

         3.SPI1

         4.SUART1

          5.使用MicroLIB库

3.代码

1.main.c文件

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file : main.c
  5. * @brief : Main program body
  6. ******************************************************************************
  7. * @attention
  8. *
  9. * Copyright (c) 2023 STMicroelectronics.
  10. * All rights reserved.
  11. *
  12. * This software is licensed under terms that can be found in the LICENSE file
  13. * in the root directory of this software component.
  14. * If no LICENSE file comes with this software, it is provided AS-IS.
  15. *
  16. ******************************************************************************
  17. */
  18. /* USER CODE END Header */
  19. /* Includes ------------------------------------------------------------------*/
  20. #include "main.h"
  21. #include "spi.h"
  22. #include "usart.h"
  23. #include "gpio.h"
  24. /* Private includes ----------------------------------------------------------*/
  25. /* USER CODE BEGIN Includes */
  26. #include "stdio.h"
  27. #include "string.h"
  28. #include "w25q128.h"
  29. /* USER CODE END Includes */
  30. /* Private typedef -----------------------------------------------------------*/
  31. /* USER CODE BEGIN PTD */
  32. /* USER CODE END PTD */
  33. /* Private define ------------------------------------------------------------*/
  34. /* USER CODE BEGIN PD */
  35. #define TEXT_SIZE 16
  36. #define FLASH_WriteAddress 0x000000 //数据写入w25q128的地址,地址范围为0x000000 ~ 0xFFFFFF
  37. #define FLASH_ReadAddress FLASH_WriteAddress
  38. /* USER CODE END PD */
  39. /* Private macro -------------------------------------------------------------*/
  40. /* USER CODE BEGIN PM */
  41. /* USER CODE END PM */
  42. /* Private variables ---------------------------------------------------------*/
  43. /* USER CODE BEGIN PV */
  44. /* USER CODE END PV */
  45. /* Private function prototypes -----------------------------------------------*/
  46. void SystemClock_Config(void);
  47. /* USER CODE BEGIN PFP */
  48. /* USER CODE END PFP */
  49. /* Private user code ---------------------------------------------------------*/
  50. /* USER CODE BEGIN 0 */
  51. //重写stdio.h文件中的prinft()里的fputc()函数
  52. int fputc(int my_data,FILE *p)
  53. {
  54. unsigned char temp = my_data;
  55. //改写后,使用printf()函数会将数据通过串口一发送出去
  56. HAL_UART_Transmit(&huart1,&temp,1,0xffff); //0xfffff为最大超时时间
  57. return my_data;
  58. }
  59. /* USER CODE END 0 */
  60. /**
  61. * @brief The application entry point.
  62. * @retval int
  63. */
  64. int main(void)
  65. {
  66. /* USER CODE BEGIN 1 */
  67. uint8_t datatemp[TEXT_SIZE];
  68. /* USER CODE END 1 */
  69. /* MCU Configuration--------------------------------------------------------*/
  70. /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  71. HAL_Init();
  72. /* USER CODE BEGIN Init */
  73. /* USER CODE END Init */
  74. /* Configure the system clock */
  75. SystemClock_Config();
  76. /* USER CODE BEGIN SysInit */
  77. /* USER CODE END SysInit */
  78. /* Initialize all configured peripherals */
  79. MX_GPIO_Init();
  80. MX_USART1_UART_Init();
  81. MX_SPI1_Init();
  82. /* USER CODE BEGIN 2 */
  83. /* w25q128初始化 */
  84. w25q128_init();
  85. /* 写入测试数据 */
  86. sprintf((char *)datatemp, "hello jiangxiao");
  87. w25q128_write(datatemp, FLASH_WriteAddress, TEXT_SIZE);
  88. printf("数据写入完成!\r\n");
  89. /* 读出测试数据 */
  90. memset(datatemp, 0, TEXT_SIZE);
  91. w25q128_read(datatemp, FLASH_ReadAddress, TEXT_SIZE);
  92. printf("读出数据:%s\r\n", datatemp);
  93. /* USER CODE END 2 */
  94. /* Infinite loop */
  95. /* USER CODE BEGIN WHILE */
  96. while (1)
  97. {
  98. /* USER CODE END WHILE */
  99. /* USER CODE BEGIN 3 */
  100. }
  101. /* USER CODE END 3 */
  102. }
  103. /**
  104. * @brief System Clock Configuration
  105. * @retval None
  106. */
  107. void SystemClock_Config(void)
  108. {
  109. RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  110. RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
  111. /** Initializes the RCC Oscillators according to the specified parameters
  112. * in the RCC_OscInitTypeDef structure.
  113. */
  114. RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  115. RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  116. RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  117. RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  118. RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  119. RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  120. RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  121. if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  122. {
  123. Error_Handler();
  124. }
  125. /** Initializes the CPU, AHB and APB buses clocks
  126. */
  127. RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
  128. |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  129. RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  130. RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  131. RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  132. RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  133. if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  134. {
  135. Error_Handler();
  136. }
  137. }
  138. /* USER CODE BEGIN 4 */
  139. /* USER CODE END 4 */
  140. /**
  141. * @brief This function is executed in case of error occurrence.
  142. * @retval None
  143. */
  144. void Error_Handler(void)
  145. {
  146. /* USER CODE BEGIN Error_Handler_Debug */
  147. /* User can add his own implementation to report the HAL error return state */
  148. __disable_irq();
  149. while (1)
  150. {
  151. }
  152. /* USER CODE END Error_Handler_Debug */
  153. }
  154. #ifdef USE_FULL_ASSERT
  155. /**
  156. * @brief Reports the name of the source file and the source line number
  157. * where the assert_param error has occurred.
  158. * @param file: pointer to the source file name
  159. * @param line: assert_param error line source number
  160. * @retval None
  161. */
  162. void assert_failed(uint8_t *file, uint32_t line)
  163. {
  164. /* USER CODE BEGIN 6 */
  165. /* User can add his own implementation to report the file name and line number,
  166. ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  167. /* USER CODE END 6 */
  168. }
  169. #endif /* USE_FULL_ASSERT */

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

  1. #include "w25q128.h"
  2. #include "spi.h"
  3. #include "stdio.h"
  4. //w25q128初始化
  5. void w25q128_init(void)
  6. {
  7. uint16_t flash_type;
  8. spi1_read_write_byte(0xFF); /* 清除DR(数据寄存器),写入一个0xFF */
  9. W25Q128_CS(1); //拉高片选信号不进行SPI通信
  10. flash_type = w25q128_read_id(); /* 读取FLASH ID. */
  11. if (flash_type == W25Q128){
  12. printf("检测到W25Q128芯片\r\n");
  13. }
  14. }
  15. //等待W25Q128空闲
  16. static void w25q128_wait_busy(void)
  17. {
  18. while ((w25q128_rd_sr1() & 0x01) == 1); /* 等待状态寄存器的BUSY位清空 */
  19. }
  20. //读取状态寄存器的值
  21. uint8_t w25q128_rd_sr1(void)
  22. {
  23. uint8_t rec_data = 0;
  24. W25Q128_CS(0);
  25. spi1_read_write_byte(FLASH_ReadStatusReg1); // 写入指令0x05:读状态寄存器1
  26. rec_data = spi1_read_write_byte(0xFF); //获取状态寄存器1的值
  27. W25Q128_CS(1);
  28. return rec_data;
  29. }
  30. //W25Q128写使能,即置位WEL为1
  31. void w25q128_write_enable(void)
  32. {
  33. W25Q128_CS(0);
  34. spi1_read_write_byte(FLASH_WriteEnable); /* 发送指令0x06:写使能 */
  35. W25Q128_CS(1);
  36. }
  37. //发送24位地址
  38. static void w25q128_send_address(uint32_t address) /*address:地址范围0~16777215字节,即寻址范围为0x00 ~ 0xFFFFFF */
  39. {
  40. spi1_read_write_byte((uint8_t)((address)>>16)); /* 发送 bit23 ~ bit16 地址 */
  41. spi1_read_write_byte((uint8_t)((address)>>8)); /* 发送 bit15 ~ bit8 地址 */
  42. spi1_read_write_byte((uint8_t)address); /* 发送 bit7 ~ bit0 地址 */
  43. }
  44. //擦除整个芯片
  45. void w25q128_erase_chip(void)
  46. {
  47. w25q128_write_enable(); /* 写使能 */
  48. w25q128_wait_busy(); /* 等待空闲 */
  49. W25Q128_CS(0);
  50. spi1_read_write_byte(FLASH_ChipErase); /* 发送指令0xC7:擦除整个芯片 */
  51. W25Q128_CS(1);
  52. w25q128_wait_busy(); /* 等待芯片擦除结束 */
  53. }
  54. //擦除一个扇区
  55. void w25q128_erase_sector(uint32_t saddr) /* saddr:该参数是第几个扇区 */
  56. {
  57. saddr *= 4096; /* 一个扇区大小为4096字节 */
  58. w25q128_write_enable(); /* 写使能 */
  59. w25q128_wait_busy(); /* 等待空闲 */
  60. W25Q128_CS(0);
  61. spi1_read_write_byte(FLASH_SectorErase); /* 发送指令0x20:擦除指定扇区 */
  62. w25q128_send_address(saddr); /* 发送擦除的扇区地址 */
  63. W25Q128_CS(1);
  64. w25q128_wait_busy(); /* 等待扇区擦除完成 */
  65. }
  66. //读取w25q128芯片ID
  67. uint16_t w25q128_read_id(void)
  68. {
  69. uint16_t deviceid;
  70. W25Q128_CS(0); //拉低片选信号进行SPI通信
  71. spi1_read_write_byte(FLASH_ManufactDeviceID); /* 发送读取 ID 命令 */
  72. /* 发送3个0 */
  73. /*
  74. spi1_read_write_byte(0);
  75. spi1_read_write_byte(0);
  76. spi1_read_write_byte(0);
  77. */
  78. w25q128_send_address(0x000000);
  79. deviceid = spi1_read_write_byte(0xFF) << 8; /* 读取高8位字节 */
  80. deviceid |= spi1_read_write_byte(0xFF); /* 读取低8位字节 */
  81. W25Q128_CS(1);
  82. return deviceid;
  83. }
  84. /*
  85. 读取W25Q128的FLASH,在指定地址开始读取指定长度的数据
  86. pubf:需要读取的数据
  87. addr:指定的地址
  88. datalen:指定的数据大小
  89. */
  90. void w25q128_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  91. {
  92. uint16_t i;
  93. W25Q128_CS(0);
  94. spi1_read_write_byte(FLASH_ReadData); /* 发送指令0x03:读取数据 */
  95. w25q128_send_address(addr); /* 发送需要读取的数据地址 */
  96. for(i=0;i<datalen;i++)
  97. {
  98. pbuf[i] = spi1_read_write_byte(0XFF); /* 循环读取 */
  99. }
  100. W25Q128_CS(1);
  101. }
  102. /*
  103. 单页写,在指定地址写入小于256字节的指定长度的数据,在非0xFF处写入的数据会失败
  104. pubf:需要写入的数据
  105. addr:指定的地址
  106. datalen:指定的数据大小
  107. */
  108. static void w25q128_write_page(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  109. {
  110. uint16_t i;
  111. w25q128_write_enable(); /* 写使能 */
  112. W25Q128_CS(0);
  113. spi1_read_write_byte(FLASH_PageProgram); /* 发送命令0x02:页写 */
  114. w25q128_send_address(addr); /* 发送写入的页地址 */
  115. for(i=0;i<datalen;i++)
  116. {
  117. spi1_read_write_byte(pbuf[i]); /* 循环写入 */
  118. }
  119. W25Q128_CS(1);
  120. w25q128_wait_busy(); /* 等待写入结束 */
  121. }
  122. /*
  123. 多页写,在指定地址写入指定长度的数据,在非0xFF处写入的数据会失败
  124. pubf:需要写入的数据
  125. addr:指定的地址
  126. datalen:指定的数据大小
  127. */
  128. static void w25q128_write_nocheck(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  129. {
  130. uint16_t pageremain;
  131. pageremain = 256 - addr % 256; /* 获取指定地址那页的剩余字节数 */
  132. if (datalen <= pageremain) /* 指定地址那页的剩余字节数能装下指定数据大小 */
  133. {
  134. pageremain = datalen;
  135. }
  136. while (1)
  137. {
  138. /* 当指定地址那页的剩余字节数能装下指定数据大小时,一次性写完 */
  139. /* 当指定数据大小比指定地址那页的剩余字节数要大时, 先写完指定地址那页的剩余字节, 然后根据剩余数据大小进行不同处理 */
  140. w25q128_write_page(pbuf, addr, pageremain); //页写
  141. if (datalen == pageremain) /* 写入结束了 */
  142. {
  143. break;
  144. }
  145. else /* datalen > pageremain */
  146. {
  147. pbuf += pageremain; /* pbuf指针地址偏移,前面已经写了pageremain字节 */
  148. addr += pageremain; /* 写地址偏移,前面已经写了pageremain字节 */
  149. datalen -= pageremain; /* 写入总长度减去已经写入了的字节数 */
  150. if (datalen > 256) /* 剩余数据大小还大于一页 */
  151. {
  152. pageremain= 256; /* 一次写入256个字节,即一次写一页 */
  153. }
  154. else /* 剩余数据大小小于一页 */
  155. {
  156. pageremain= datalen; /* 一次性写完 */
  157. }
  158. }
  159. }
  160. }
  161. /*
  162. //写入W25Q128的FLASH,在指定地址开写入取指定长度的数据
  163. pubf:需要写入的数据
  164. addr:指定的地址
  165. datalen:指定的数据大小
  166. */
  167. uint8_t g_w25q128_buf[4096]; /* 扇区缓存 */
  168. void w25q128_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
  169. {
  170. uint32_t secpos;
  171. uint16_t secoff;
  172. uint16_t secremain;
  173. uint16_t i;
  174. uint8_t *w25q128_buf;
  175. w25q128_buf = g_w25q128_buf;
  176. secpos = addr / 4096; /* 获取指定地址在哪片扇区 */
  177. secoff = addr % 4096; /* 指定数据在在扇区内的偏移数据大小 */
  178. secremain = 4096 - secoff; /* 扇区剩余字节数 */
  179. if (datalen <= secremain) /* 指定地址那片扇区的剩余字节数能装下指定数据大小 */
  180. {
  181. secremain = datalen;
  182. }
  183. while (1)
  184. {
  185. w25q128_read(w25q128_buf, secpos * 4096, 4096); /* 读出指定地址那片扇区的全部内容 */
  186. for (i = 0; i < secremain; i++) /* 校验数据,防止数据出现非0xFF */
  187. {
  188. if (w25q128_buf[secoff + i] != 0xFF) //扇区数据有一个数据不是0xFF
  189. {
  190. break; /* 需要擦除, 直接退出for循环 */
  191. }
  192. }
  193. if (i < secremain) /* 需要擦除 */
  194. {
  195. w25q128_erase_sector(secpos); /* 擦除这个扇区 */
  196. for (i = 0; i < secremain; i++) /* 复制 */
  197. {
  198. w25q128_buf[i + secoff] = pbuf[i];
  199. }
  200. w25q128_write_nocheck(w25q128_buf, secpos * 4096, 4096); /* 写入整个扇区 */
  201. }
  202. else /* 写已经擦除了的,直接写入扇区剩余区间. */
  203. {
  204. w25q128_write_nocheck(pbuf, addr, secremain); /* 直接写扇区 */
  205. }
  206. if (datalen == secremain)
  207. {
  208. break; /* 写入结束了 */
  209. }
  210. else /* 写入未结束 */
  211. {
  212. secpos++; /* 扇区地址增1,新的一个扇区 */
  213. secoff = 0; /* 偏移位置为0 */
  214. pbuf += secremain; /* 指针偏移 */
  215. addr += secremain; /* 写地址偏移 */
  216. datalen -= secremain; /* 字节数递减 */
  217. if (datalen > 4096)
  218. {
  219. secremain = 4096; /* 一次写入一个扇区 */
  220. }
  221. else
  222. {
  223. secremain = datalen;/* 一次性写完 */
  224. }
  225. }
  226. }
  227. }

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

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

4.spi.c文件编写

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file spi.c
  5. * @brief This file provides code for the configuration
  6. * of the SPI instances.
  7. ******************************************************************************
  8. * @attention
  9. *
  10. * Copyright (c) 2023 STMicroelectronics.
  11. * All rights reserved.
  12. *
  13. * This software is licensed under terms that can be found in the LICENSE file
  14. * in the root directory of this software component.
  15. * If no LICENSE file comes with this software, it is provided AS-IS.
  16. *
  17. ******************************************************************************
  18. */
  19. /* USER CODE END Header */
  20. /* Includes ------------------------------------------------------------------*/
  21. #include "spi.h"
  22. /* USER CODE BEGIN 0 */
  23. /* USER CODE END 0 */
  24. SPI_HandleTypeDef hspi1; /* SPI句柄 */
  25. /* SPI1 init function */
  26. void MX_SPI1_Init(void)
  27. {
  28. /* USER CODE BEGIN SPI1_Init 0 */
  29. /* USER CODE END SPI1_Init 0 */
  30. /* USER CODE BEGIN SPI1_Init 1 */
  31. /* USER CODE END SPI1_Init 1 */
  32. hspi1.Instance = SPI1;
  33. hspi1.Init.Mode = SPI_MODE_MASTER; /* 设置SPI模式主机模式 */
  34. hspi1.Init.Direction = SPI_DIRECTION_2LINES; /* 设置SPI工作方式:全双工 */
  35. hspi1.Init.DataSize = SPI_DATASIZE_8BIT; /* 设置数据格式:8bit */
  36. /* SPI模式1 */
  37. hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; /* 设置时钟极性:CPOL = 0 */
  38. hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; /* 设置时钟相位:CPHA = 1 */
  39. hspi1.Init.NSS = SPI_NSS_SOFT; /* 设置片选方式:软件NSS */
  40. hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; /* 设置SPI时钟波特率分频:256分频 */
  41. hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; /* 设置数据大小端:MSB */
  42. hspi1.Init.TIMode = SPI_TIMODE_DISABLE; /* 设置数据帧格式:Motolora */
  43. hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; /* 设置CRC校验:关闭CRC检验 */
  44. hspi1.Init.CRCPolynomial = 10; /* 设置CRC校验多项式:1~65535 */
  45. if (HAL_SPI_Init(&hspi1) != HAL_OK)
  46. {
  47. Error_Handler();
  48. }
  49. /* USER CODE BEGIN SPI1_Init 2 */
  50. /* USER CODE END SPI1_Init 2 */
  51. }
  52. void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
  53. {
  54. GPIO_InitTypeDef GPIO_InitStruct = {0};
  55. if(spiHandle->Instance==SPI1)
  56. {
  57. /* USER CODE BEGIN SPI1_MspInit 0 */
  58. /* USER CODE END SPI1_MspInit 0 */
  59. /* SPI1 clock enable */
  60. __HAL_RCC_SPI1_CLK_ENABLE();
  61. __HAL_RCC_GPIOA_CLK_ENABLE();
  62. /**SPI1 GPIO Configuration
  63. PA5 ------> SPI1_SCK
  64. PA6 ------> SPI1_MISO
  65. PA7 ------> SPI1_MOSI
  66. */
  67. GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_7;
  68. GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
  69. GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  70. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  71. GPIO_InitStruct.Pin = GPIO_PIN_6;
  72. GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  73. GPIO_InitStruct.Pull = GPIO_NOPULL;
  74. HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
  75. /* USER CODE BEGIN SPI1_MspInit 1 */
  76. /* USER CODE END SPI1_MspInit 1 */
  77. }
  78. }
  79. void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
  80. {
  81. if(spiHandle->Instance==SPI1)
  82. {
  83. /* USER CODE BEGIN SPI1_MspDeInit 0 */
  84. /* USER CODE END SPI1_MspDeInit 0 */
  85. /* Peripheral clock disable */
  86. __HAL_RCC_SPI1_CLK_DISABLE();
  87. /**SPI1 GPIO Configuration
  88. PA5 ------> SPI1_SCK
  89. PA6 ------> SPI1_MISO
  90. PA7 ------> SPI1_MOSI
  91. */
  92. HAL_GPIO_DeInit(GPIOA, GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7);
  93. /* USER CODE BEGIN SPI1_MspDeInit 1 */
  94. /* USER CODE END SPI1_MspDeInit 1 */
  95. }
  96. }
  97. /* USER CODE BEGIN 1 */
  98. /*通过SPI1同时读写一个字节数据
  99. 主机只向从机进行写操作,调用此函数时忽略返回值
  100. 主机只向从机进行读操作,调用此函数时随便传入一个字符,尽量是0xFF
  101. */
  102. uint8_t spi1_read_write_byte(uint8_t data)
  103. {
  104. uint8_t rec_data = 0;
  105. HAL_SPI_TransmitReceive(&hspi1, &data, &rec_data, 1, 1000); //spi读写数据函数,参数2存放用来发送的数据,参数3存放用来接收的数据
  106. return rec_data;
  107. }
  108. /* USER CODE END 1 */

5.spi.h文件编写

  1. /* USER CODE BEGIN Header */
  2. /**
  3. ******************************************************************************
  4. * @file spi.h
  5. * @brief This file contains all the function prototypes for
  6. * the spi.c file
  7. ******************************************************************************
  8. * @attention
  9. *
  10. * Copyright (c) 2023 STMicroelectronics.
  11. * All rights reserved.
  12. *
  13. * This software is licensed under terms that can be found in the LICENSE file
  14. * in the root directory of this software component.
  15. * If no LICENSE file comes with this software, it is provided AS-IS.
  16. *
  17. ******************************************************************************
  18. */
  19. /* USER CODE END Header */
  20. /* Define to prevent recursive inclusion -------------------------------------*/
  21. #ifndef __SPI_H__
  22. #define __SPI_H__
  23. #ifdef __cplusplus
  24. extern "C" {
  25. #endif
  26. /* Includes ------------------------------------------------------------------*/
  27. #include "main.h"
  28. /* USER CODE BEGIN Includes */
  29. /* USER CODE END Includes */
  30. extern SPI_HandleTypeDef hspi1;
  31. /* USER CODE BEGIN Private defines */
  32. /* USER CODE END Private defines */
  33. void MX_SPI1_Init(void);
  34. /* USER CODE BEGIN Prototypes */
  35. uint8_t spi1_read_write_byte(uint8_t data);
  36. /* USER CODE END Prototypes */
  37. #ifdef __cplusplus
  38. }
  39. #endif
  40. #endif /* __SPI_H__ */

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

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

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

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

 

        4.添加头文件

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

        例如下图

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/630773
推荐阅读
相关标签
  

闽ICP备14008679号