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

一个主设备和多个从设备

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
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字节)来进行擦除。

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文件
/* 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:读状态寄存器1rec_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 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 ConfigurationPA5 ------> SPI1_SCKPA6 ------> SPI1_MISOPA7 ------> 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 ConfigurationPA5 ------> SPI1_SCKPA6 ------> SPI1_MISOPA7 ------> 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的文件夹

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

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


4.添加头文件
只需在需要添加头文件的c文件中写上需添加的头文件再编译就能自动添加
例如下图
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
