当前位置: 首页 > news >正文

STM32之SPI

SPI

SPI介绍

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

SPI物理架构

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

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

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

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

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

SPI工作原理

SPI主从模式

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

SPI主、从设备通讯接线

一个主设备和一个从设备

一个主设备和多个从设备

SPI数据传输

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

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

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

  1. 数据交换

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

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

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

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

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

SPI工作模式

时钟极性(CPOL)

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

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

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

时钟相位(CPHA)

时钟线在第几个时钟边沿采样信号。

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

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

SPI模式

SPI模式

CPOL

CPHA

空闲时SCK时钟

采样时刻

模式0

0

0

低电平

奇数边沿

模式1

0

1

低电平

偶数边沿

模式2

1

0

高电平

奇数边沿

模式3

1

1

高电平

偶数边沿

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

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

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

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

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

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

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

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

W25Q128

W25Q128介绍

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

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

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

W25Q128存储架构

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

  • 16777216 -1 = 0xFFFFFF。

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

W25Q128状态寄存器

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

BUSY位

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

  • 0:空闲

  • 1:忙

WEL位

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

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

  • 0:禁止写入

W25Q128常用指令

W25Q128有非常多的指令,在这里介绍几个常用的指令。

指令

名称

作用

0x06

写使能

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

0x05

读SR1

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

0x03

读数据

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

0x02

页写

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

0x20

扇区擦除

扇区擦除指令将指定扇区(4K字节)内的所有数据都擦除为0xFF。

0xC7

芯片擦除

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

0x90

读取芯片ID

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

写使能(0x06)

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

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

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

读SR1(0x05

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

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

读数据(0x03

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

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

页写(0x02

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

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

扇区擦除(0x20

  • 扇区擦除指令将指定扇区(4K字节)内的所有数据都擦除为0xFF。

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

芯片擦除(0xC7)

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

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

读取W25Q128的芯片ID(0x90)

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

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

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

STM32的hal库关于SPI的函数

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;
}

W25Q128与STM32F103C8T6板子接线

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

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

  • 3.3V <-> VCC

  • GND <-> GND

  • PA4 <-> CS

  • PA5 <-> CLK

  • PA6 <-> DO(MISO)

  • PA7 <-> DI(MOSI)

STM32CubeMX相关配置

配置SYS

配置RCC

配置GPIO

配置PA4为输出高电平,用来作为SPI主机的片选信号线(CS)。

配置串口信息(UART1)

配置SPI

  • LSB:全称为Least Significant Bit,在二进制数中意为最低有效位,一般来说,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。

  • MSB:全称为Most Significant Bit,在二进制数中属于最高有效位,MSB是最高加权位,与十进制数字中最左边的一位类似。

配置工程名称、工程路径

选择固件库

生成工程

使用MicroLIB库

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 */

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<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;/* 一次性写完 */}}}
}

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,在指定地址开写入取指定长度的数据

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;/* 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;hspi1.Init.Direction = SPI_DIRECTION_2LINES;hspi1.Init.DataSize = SPI_DATASIZE_8BIT;hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;hspi1.Init.NSS = SPI_NSS_SOFT;hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256;hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;hspi1.Init.TIMode = SPI_TIMODE_DISABLE;hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;hspi1.Init.CRCPolynomial = 10;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 */

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的文件夹

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

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

相关文章:

STM32之SPI

SPISPI介绍SPI是串行外设接口(Serial Peripherallnterface)的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&#xff0c;同时为PCB的布局上节省空间&#xff0c;提供方便…...

02 深度学习环境搭建

1、查看对应版本关系 详细见&#xff1a;https://blog.csdn.net/qq_41946216/article/details/129476095?spm1001.2014.3001.5501此案例环境使用 CUDA 11.7、Pytouch1.12.1、Miniconda3_py38(含Python3.8) 2. 安装Anaconda 或 Miniconda 本案例重点一为Miniconda准 2.1 安…...

PHP导入大量CSV数据的方法分享

/** * @description 迭代器读取csv文件 * @param $strCsvPath * @return \Generator */ public static function readPathCsvFile($strCsvPath) { if ($handle = fopen($strCsvPath, r)) { while (!feof($handle)) { yield fgetcsv($handle); } …...

代码看不懂?ChatGPT 帮你解释,详细到爆!

偷个懒&#xff0c;用ChatGPT 帮我写段生物信息代码如果 ChatGPT 给出的的代码不太完善&#xff0c;如何请他一步步改好&#xff1f;网上看到一段代码&#xff0c;不知道是什么含义&#xff1f;输入 ChatGPT 帮我们解释下。生信宝典 1: 下面是一段 Linux 代码&#xff0c;请帮…...

【MyBatis】篇三.自定义映射resultMap和动态SQL

MyBatis整理 篇一.MyBatis环境搭建与增删改查 篇二.MyBatis查询与特殊SQL 篇三.自定义映射resultMap和动态SQL 篇四.MyBatis缓存和逆向工程 文章目录1、自定义映射P1:测试数据准备P2:字段和属性的映射关系P3:多对一的映射关系P4:一对多的映射关系2、动态SQL2.1 IF标签2.2 w…...

什么是API?(详细解说)

编程资料时经常会看到API这个名词&#xff0c;网上各种高大上的解释估计放倒了一批初学者。初学者看到下面这一段话可能就有点头痛了。 API&#xff08;Application Programming Interface,应用程序编程接口&#xff09;是一些预先定义的函数&#xff0c;目的是提供应用程序与开…...

比cat更好用的命令!

大家好&#xff0c;我是良许。 作为程序员&#xff0c;大家一定对 cat 这个命令不陌生。它主要的功能就是用来显示文本文件的具体内容。 但 cat 命令两个很重大的缺陷&#xff1a;1. 不能语法高亮输出&#xff1b;2. 文本太长的话无法翻页输出。正是这两个不足&#xff0c;使…...

MySQL、HBase、ElasticSearch三者对比

1、概念介绍 MySQL&#xff1a;关系型数据库&#xff0c;主要面向OLTP&#xff0c;支持事务&#xff0c;支持二级索引&#xff0c;支持sql&#xff0c;支持主从、Group Replication架构模型&#xff08;本文全部以Innodb为例&#xff0c;不涉及别的存储引擎&#xff09;。 HBas…...

Vue+ElementUI+Vuex购物车

最完整最能理解的Vuex版本的购物车购物车是最经典的小案例。Vuex代码&#xff1a;import Vue from vue import Vuex from vuex import $http from ../request/http Vue.use(Vuex)const store new Vuex.Store({state:{shopList:[],},mutations:{setShopCarList(state,payload)…...

Android 录屏 实现

https://lixiaogang03.github.io/2021/11/02/Android-%E5%BD%95%E5%B1%8F/ https://xie.infoq.cn/article/dd40cd5d753c896225063f696 视频地址&#xff1a; https://time.geekbang.org/dailylesson/detail/100056832 概述 在视频会议、线上课堂、游戏直播等场景下&#x…...

【CSAPP】家庭作业2.55~2.76

文章目录2.55*2.56*2.57*2.58**2.59**2.60**位级整数编码规则2.61**2.62***2.63***2.64*2.65****2.66***2.67**2.68**2.69***2.70**2.71*2.72**2.73**2.74**2.75***2.76*2.55* 问&#xff1a;在你能访问的不同的机器上&#xff0c;编译show_bytes.c并运行代码&#xff0c;确定…...

Python操作MySQL数据库详细案例

Python操作MySQL数据库详细案例一、前言二、数据准备三、建立数据库四、处理和上传数据五、下载数据六、完整项目数据和代码一、前言 本文通过案例讲解如何使用Python操作MySQL数据库。具体任务为&#xff1a;假设你已经了解MySQL和知识图谱标注工具Brat&#xff0c;将Brat标注…...

MicroBlaze系列教程(8):AXI_CAN的使用

文章目录 @[toc]CAN总线概述AXI_CAN简介MicroBlaze硬件配置常用函数使用示例波形实测参考资料工程下载本文是Xilinx MicroBlaze系列教程的第8篇文章。 CAN总线概述 **CAN(Controller Area Network)**是 ISO 国际标准化的串行通信协议,是由德国博世(BOSCH)公司在20世纪80年代…...

网络安全领域中八大类CISP证书

CISP​注册信息安全专业人员 注册信息安全专业人员&#xff08;Certified Information Security Professional&#xff09;&#xff0c;是经中国信息安全产品测评认证中心实施的国家认证&#xff0c;对信息安全人员执业资质的认可。该证书是面向信息安全企业、信息安全咨询服务…...

stm32学习笔记-5EXIT外部中断

5 EXIT外部中断 [toc] 注&#xff1a;笔记主要参考B站 江科大自化协 教学视频“STM32入门教程-2023持续更新中”。 注&#xff1a;工程及代码文件放在了本人的Github仓库。 5.1 STM32中断系统 图5-1 中断及中断嵌套示意图 中断 是指在主程序运行过程中&#xff0c;出现了特定…...

MySQL Workbench 图形化界面工具

Workbench 介绍 MySQL官方提供了一款免费的图形工具——MySQL Workbench&#xff0c;它是一款功能强大且易于使用的数据库设计、管理和开发工具&#xff0c;总之&#xff0c;MySQL Workbench是一款非常好用的MySQL图形工具&#xff0c;可以满足大多数MySQL用户的需求。 目录 W…...

雪花算法(SnowFlake)

简介现在的服务基本是分布式、微服务形式的&#xff0c;而且大数据量也导致分库分表的产生&#xff0c;对于水平分表就需要保证表中 id 的全局唯一性。对于 MySQL 而言&#xff0c;一个表中的主键 id 一般使用自增的方式&#xff0c;但是如果进行水平分表之后&#xff0c;多个表…...

Linux防火墙

一、Linux防火墙Linux的防火墙体系主要在网络层&#xff0c;针对TCP/IP数据包实施过滤和限制&#xff0c;属于典型的包过滤防火墙&#xff08;或称为网络层防火墙&#xff09;。Linux系统的防火墙体系基于内核编码实现&#xff0c;具有非常稳定的性能和极高的效率&#xff0c;因…...

网络安全系列-四十七: IP协议号大全

IP协议号列表 这是用在IPv4头部和IPv6头部的下一首部域的IP协议号列表。 十进制十六进制关键字协议引用00x00HOPOPTIPv6逐跳选项RFC 246010x01ICMP互联网控制消息协议(ICMP)RFC 79220x02IGMP...

HTTP协议格式以及Fiddler用法

目录 今日良言:焦虑和恐惧改变不了明天,唯一能做的就是把握今天 一、HTTP协议的基本格式 二、Fiddler的用法 1.Fidder的下载 2.Fidder的使用 今日良言:焦虑和恐惧改变不了明天,唯一能做的就是把握今天 一、HTTP协议的基本格式 先来介绍一下http协议: http 协议(全称为 &q…...

自动写代码?别闹了!

大家好&#xff0c;我是良许。 这几天&#xff0c;GitHub 上有个很火的插件在抖音刷屏了——Copilot。 这个神器有啥用呢&#xff1f;简单来讲&#xff0c;它就是一款由人工智能打造的编程辅助工具。 我们来看看它有啥用。 首先就是代码补全功能&#xff0c;你只要给出函数…...

项目心得--网约车

一、RESTFULPost&#xff1a;新增Put&#xff1a;全量修改Patch&#xff1a;修改某个值Delete: 删除Get&#xff1a;查询删除接口也可以用POST请求url注意&#xff1a;url中不要带有敏感词&#xff08;用户id等&#xff09;url中的名词用复数形式url设计&#xff1a;api.xxx.co…...

【二叉树广度优先遍历和深度优先遍历】

文章目录一、二叉树的深度优先遍历0.建立一棵树1. 前序遍历2.中序遍历3. 后序遍历二、二叉树的广度优先遍历层序遍历三、有关二叉树练习一、二叉树的深度优先遍历 学习二叉树结构&#xff0c;最简单的方式就是遍历。 所谓二叉树遍历(Traversal)是按照某种特定的规则&#xff…...

Spring Cloud微服务架构必备技术

单体架构 单体架构&#xff0c;也叫单体应用架构&#xff0c;是一个传统的软件架构模式。单体架构是指将应用程序的所有组件部署到一个单一的应用程序中&#xff0c;并统一进行部署、维护和扩展。在单体架构中&#xff0c;应用程序的所有功能都在同一个进程中运行&#xff0c;…...

TCP三次握手与四次挥手(一次明白)

TCP基本信息 默认端口号:80 LINUX中TIME_WAIT的默认时间是30s TCP三次握手 三次握手过程:每行代表发起握手到另一方刚刚收到数据包时的状态 客户端服务端客户端状态服务端状态握手前CLOSELISTEN客户端发送带有SYN标志的数据包到服务端一次握手SYN_SENDLISTEN二次握手服务端发送…...

pyside6@Mouse events实例@QApplication重叠导致的报错@keyboardInterrupt

文章目录报错内容鼠标事件演示报错内容 在pyside图形界面应用程序开发过程中,通常只允许运行一个实例 假设您重复执行程序A,那么可能会导致一些意向不到的错误并且,从python反馈的信息不容易判断错误的真正来源 鼠标事件演示 下面是一段演示pyside6的鼠标事件mouseEvent对象…...

订单30分钟未支付自动取消怎么实现?

目录了解需求方案 1&#xff1a;数据库轮询方案 2&#xff1a;JDK 的延迟队列方案 3&#xff1a;时间轮算法方案 4&#xff1a;redis 缓存方案 5&#xff1a;使用消息队列了解需求在开发中&#xff0c;往往会遇到一些关于延时任务的需求。例如生成订单 30 分钟未支付&#xff0…...

< 开源项目框架:推荐几个开箱即用的开源管理系统 - 让开发不再复杂 >

文章目录&#x1f449; SCUI Admin 中后台前端解决方案&#x1f449; Vue .NetCore 前后端分离的快速发开框架&#x1f449; next-admin 适配移动端、pc的后台模板&#x1f449; django-vue-admin-pro 快速开发平台&#x1f449; Admin.NET 通用管理平台&#x1f449; RuoYi 若…...

内网渗透-基础环境

解决依赖&#xff0c;scope安装 打开要给cmd powershell 打开远程 Set-ExecutionPolicy RemoteSigned -scope CurrentUser; 我试了好多装这东西还是得科学上网&#xff0c;不然不好用 iwr -useb get.scoop.sh | iex 查看下载过的软件 安装sudo 安装git 这里一定要配置bu…...

Go语言学习的第一天(对于Go学习的认识和工具选择及环境搭建)

首先学习一门新的语言&#xff0c;我们要知道这门语言可以帮助我们做些什么&#xff1f;为什么我们要学习这门语言&#xff1f;就小wei而言学习这门语言是为了区块链&#xff0c;因为自身是php出身&#xff0c;因为php的一些特性只能通过一些算法模拟的做一个虚拟链&#xff0c…...

湘潭外包网络推广公司/seo推广是什么意思

多线程 当我们写的爬虫是单线程的时候&#xff0c;一旦到一个地方卡的不动了&#xff0c;那就永远的等下去吧&#xff0c;我们可以使用多线程来改变这个问题。 爬虫使用多线程来处理网络请求&#xff0c;使用线程来处理URL队列中的url&#xff0c;然后将url返回的结果保存再另…...

珠海移动网站建设公司排名/地推团队去哪里找

有时候我们的网站程序在本地运行没有问题&#xff0c;但在上传到远程服务器后则报错。这就需要我们了解具体错误&#xff0c;但IIS默认只显示统一的运行时错误&#xff0c;想要知道具体错误就需要配置Web.config中customErrors mode选项为Off。不过有时候customErrors标签是被包…...

内部卷网站怎么做的/seo指搜索引擎

原文地址&#xff1a;php 冒泡 插入 交换法 选择法 几种排序作者&#xff1a;我爱高进// 冒泡排序 function BubbleSort($arr) { // 获得数组总长度 $num count($arr); // 正向遍历数组 for ($i 1; $i < $num; $i) { // 反向遍历 for ($j…...

成都网站建设时代汇创/百度广告点击一次多少钱

// 克隆已有仓库 git clone 仓库地址 // 暂存区 git add . // 本地仓库 git commit -m "详情描述" // 拉下来的代码是否最新 git pull // 推送远程仓库 git push// 查看分支 git branch // 切换分支 git checkout 分支名 // 暂存区 git add . // 本地仓库 git commit…...

敦煌网站做外贸怎样/如何做公司网站推广

Map集合 1. Map接口的概述 将键映射到值的对象。 Map不能包含重复的键; 每个键可以映射到最多一个值。 举例&#xff1a; <K,V> 键值对 1001 王宇 1002 周家祥 1001 周家祥&#xff08;不允许&#xff09; Map集合中&#xff0c;K的值不能重复 2. Map接口与Collection接…...

菏泽哪里做网站/江苏建站

最近参与一个开源项目&#xff0c;一个功能的实现&#xff0c;用到了 druid 解析器来解析SQL&#xff0c;记录下如果使用 druid 来解析SQL&#xff0c;实现对SQL的拦截改写。1. 对 insert 语句进行解析&#xff1a;private static String convertInsertSQL(String sql){try{MyS…...