主页 > 创业  > 

STM32之SPI

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通讯。

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

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

数据交换

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

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

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

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

如果只进行读操作,只需发送一个空字节来获取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:读状态寄存器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;/* 一次性写完 */ } } } }

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

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

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

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

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

标签:

STM32之SPI由讯客互联创业栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“STM32之SPI