STM32(十):SPI (标准库函数)

前言

上一篇文章已经介绍了如何用STM32单片机中USART通信协议来串口通信,并向XCOM串口助手发送信息。这篇文章我们来介绍一下如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带。

一、实验原理

串行通信之前的博客里有所介绍,可以查看以下链接

STM32(九):USART串口通信 (标准库函数)-CSDN博客

1.SPI的介绍

SPI(Serial Peripheral interface),顾名思义就是串行外围设备接口。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

2.SPI的通信原理

通常SPI通过4个引脚与外部器件相连:

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

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

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

● NSS: 从设备选择。这是一个可选的引脚,用来选择主/从设备。它的功能是用来作为“片选引脚”,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。从设备的NSS引脚可以由主设备的一个标准I/O引脚来驱动。

SPI有主、从两种模式

  • 作为主机时,使用一个IO引脚拉低相应从机的选择引脚(NSS),传输的起始由主机发送数据来启动,时钟(SCK)信号由主机产生。通过MOSI发送数据,同时通过MISO引脚接收从机发出的数据。    
  • 作为从机时,传输在从机选择引脚(NSS)被主机拉低后开始,接收主机输出的时钟信号,在读取主机数据的同时通过MISO引脚输出数据。

下图中简单模拟SPI通信流程,主机拉低NSS片选信号,启动通信,并且产生时钟信号,上升沿触发边沿信号,主机在MOSI线路一位一位发送数据0X53,在MISO线路一位一位接收数据0X46。

3.SPI的时序

数据时钟时序图

通过组合CPOL和CPHA的设置,可以产生四种可能的时序关系,这影响数据的采样和锁存时机。这些设置对SPI主模式和从模式下的设备都适用。

  • CPOL(时钟极性):控制在没有数据传输时SCK(时钟)引脚的空闲状态电平。
  1. CPOL=0:SCK引脚在空闲状态保持低电平。
  2. CPOL=1:SCK引脚在空闲状态保持高电平。
  • CPHA(时钟相位):确定数据位的采样和锁存时机。
  1. CPHA=0:数据在第一个时钟边沿被采样和锁存。第一个边沿是下降沿如果CPOL=0,是上升沿如果CPOL=1。
  2. CPHA=1:数据在第二个时钟边沿被采样和锁存。第二个边沿是下降沿如果CPOL=0,是上升沿如果CPOL=1。

4.SPI中断

SPI共有5种中断事件,如下图所示:

二、实验步骤

1.串行FLASH初始化

void SPI_FLASH_Init(void)
{SPI_InitTypeDef  SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;	/* 使能GPIO和SPI时钟 */FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );FLASH_SPI_SCK_APBxClock_FUN ( FLASH_SPI_SCK_CLK, ENABLE );FLASH_SPI_MISO_APBxClock_FUN ( FLASH_SPI_MISO_CLK, ENABLE );FLASH_SPI_MOSI_APBxClock_FUN ( FLASH_SPI_MOSI_CLK, ENABLE );FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK, ENABLE );/* 配置SPI功能引脚:SCK 时钟引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:MISO 主机输入从机输出引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:MISO 主机输出从机输入引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:CS 串行Flash片选引脚 */	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;	GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);/* 首先禁用串行Flash,等需要操作串行Flash时再使能即可 */FLASH_SPI_CS_DISABLE();/* SPI外设配置 *//* * FLASH芯片:* 在CLK上升沿时到DIO数据采样输入. * 在CLK下降沿时在DIO进行数据输出。* 据此设置CPOL CPHA */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(FLASH_SPIx , &SPI_InitStructure);/* 使能SPI外设  */SPI_Cmd(FLASH_SPIx , ENABLE);	
}

2.擦除串行Flash整片空间

void SPI_FLASH_BulkErase(void)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 整片擦除 Erase *//* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送整片擦除指令*/SPI_FLASH_SendByte(W25X_ChipErase);/* 禁用串行FLASH: CS高电平 */FLASH_SPI_CS_DISABLE();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}

3.写入数据

调用本函数写入数据前需要先擦除扇区

void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = WriteAddr % SPI_FLASH_PageSize;count = SPI_FLASH_PageSize - Addr;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}else /* 若地址与 SPI_FLASH_PageSize 不对齐 */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */{temp = NumOfSingle - count;SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}
}

4.读取数据

void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送 读 指令 */SPI_FLASH_SendByte(W25X_ReadData);/* 发送 读 地址高位 */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* 发送 读 地址低位 */SPI_FLASH_SendByte(ReadAddr & 0xFF);while (NumByteToRead--) /* 读取数据 */{/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 禁用串行FLASH: CS 高电平 */FLASH_SPI_CS_DISABLE();
}

5.使能串行Flash写操作

void SPI_FLASH_WriteEnable(void)
{/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送命令:写使能 */SPI_FLASH_SendByte(W25X_WriteEnable);/* 禁用串行Flash:CS高电平 */FLASH_SPI_CS_DISABLE();
}

三、实操代码

程序分为3个文件:bsp_spi_flash.c、bsp_spi_flash.h、main.c

1.bsp_spi_flash.c 

/* 包含头文件 ----------------------------------------------------------------*/
#include "bsp/spi_flash/bsp_spi_flash.h"/* 私有类型定义 --------------------------------------------------------------*/
/* 私有宏定义 ----------------------------------------------------------------*/
#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
#define W25X_WriteEnable		            0x06 
#define W25X_WriteDisable		            0x04 
#define W25X_ReadStatusReg		          0x05 
#define W25X_WriteStatusReg		          0x01 
#define W25X_ReadData			              0x03 
#define W25X_FastReadData		            0x0B 
#define W25X_FastReadDual		            0x3B 
#define W25X_PageProgram		            0x02 
#define W25X_BlockErase			            0xD8 
#define W25X_SectorErase		            0x20 
#define W25X_ChipErase			            0xC7 
#define W25X_PowerDown			            0xB9 
#define W25X_ReleasePowerDown	          0xAB 
#define W25X_DeviceID			              0xAB 
#define W25X_ManufactDeviceID   	      0x90 
#define W25X_JedecDeviceID		          0x9F #define WIP_Flag                        0x01  /* Write In Progress (WIP) flag */#define Dummy_Byte                      0xFF
/* 私有变量 ------------------------------------------------------------------*/
/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
/* 函数体 --------------------------------------------------------------------*//*** 函数功能: 串行FLASH初始化* 输入参数: 无* 返 回 值: uint32_t:返回串行Flash型号ID* 说    明:初始化串行Flash底层驱动GPIO和SPI外设*/
void SPI_FLASH_Init(void)
{SPI_InitTypeDef  SPI_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;	/* 使能GPIO和SPI时钟 */FLASH_SPI_APBxClock_FUN ( FLASH_SPI_CLK, ENABLE );FLASH_SPI_SCK_APBxClock_FUN ( FLASH_SPI_SCK_CLK, ENABLE );FLASH_SPI_MISO_APBxClock_FUN ( FLASH_SPI_MISO_CLK, ENABLE );FLASH_SPI_MOSI_APBxClock_FUN ( FLASH_SPI_MOSI_CLK, ENABLE );FLASH_SPI_CS_APBxClock_FUN ( FLASH_SPI_CS_CLK, ENABLE );/* 配置SPI功能引脚:SCK 时钟引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_Init(FLASH_SPI_SCK_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:MISO 主机输入从机输出引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;GPIO_Init(FLASH_SPI_MISO_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:MISO 主机输出从机输入引脚 */	GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;GPIO_Init(FLASH_SPI_MOSI_PORT, &GPIO_InitStructure);/* 配置SPI功能引脚:CS 串行Flash片选引脚 */	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStructure.GPIO_Pin = FLASH_SPI_CS_PIN;	GPIO_Init(FLASH_SPI_CS_PORT, &GPIO_InitStructure);/* 首先禁用串行Flash,等需要操作串行Flash时再使能即可 */FLASH_SPI_CS_DISABLE();/* SPI外设配置 *//* * FLASH芯片:* 在CLK上升沿时到DIO数据采样输入. * 在CLK下降沿时在DIO进行数据输出。* 据此设置CPOL CPHA */SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStructure.SPI_Mode = SPI_Mode_Master;SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStructure.SPI_CRCPolynomial = 7;SPI_Init(FLASH_SPIx , &SPI_InitStructure);/* 使能SPI外设  */SPI_Cmd(FLASH_SPIx , ENABLE);	
}/*** 函数功能: 擦除扇区* 输入参数: SectorAddr:待擦除扇区地址,要求为4096倍数* 返 回 值: 无* 说    明:串行Flash最小擦除块大小为4KB(4096字节),即一个扇区大小,要求输入参数*           为4096倍数。在往串行Flash芯片写入数据之前要求先擦除空间。*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送扇区擦除指令*/SPI_FLASH_SendByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI_FLASH_SendByte((SectorAddr & 0xFF0000) >> 16);/* 发送擦除扇区地址的中位 */SPI_FLASH_SendByte((SectorAddr & 0xFF00) >> 8);/* 发送擦除扇区地址的低位 */SPI_FLASH_SendByte(SectorAddr & 0xFF);/* 禁用串行FLASH: CS 高电平 */FLASH_SPI_CS_DISABLE();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** 函数功能: 擦除整片* 输入参数: 无* 返 回 值: 无* 说    明:擦除串行Flash整片空间*/
void SPI_FLASH_BulkErase(void)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 整片擦除 Erase *//* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送整片擦除指令*/SPI_FLASH_SendByte(W25X_ChipErase);/* 禁用串行FLASH: CS高电平 */FLASH_SPI_CS_DISABLE();/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** 函数功能: 往串行FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* 输入参数: pBuffer:待写入数据的指针*           WriteAddr:写入地址*           NumByteToWrite:写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* 返 回 值: 无* 说    明:串行Flash每页大小为256个字节*/
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{/* 发送FLASH写使能命令 */SPI_FLASH_WriteEnable();/* 寻找串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 写送写指令*/SPI_FLASH_SendByte(W25X_PageProgram);/*发送写地址的高位*/SPI_FLASH_SendByte((WriteAddr & 0xFF0000) >> 16);/*发送写地址的中位*/SPI_FLASH_SendByte((WriteAddr & 0xFF00) >> 8);/*发送写地址的低位*/SPI_FLASH_SendByte(WriteAddr & 0xFF);if(NumByteToWrite > SPI_FLASH_PerWritePageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;//printf("Err: SPI_FLASH_PageWrite too large!\n");}/* 写入数据*/while (NumByteToWrite--){/* 发送当前要写入的字节数据 */SPI_FLASH_SendByte(*pBuffer);/* 指向下一字节数据 */pBuffer++;}/* 禁用串行FLASH: CS 高电平 */FLASH_SPI_CS_DISABLE();/* 等待写入完毕*/SPI_FLASH_WaitForWriteEnd();
}/*** 函数功能: 往串行FLASH写入数据,调用本函数写入数据前需要先擦除扇区* 输入参数: pBuffer:待写入数据的指针*           WriteAddr:写入地址*           NumByteToWrite:写入数据长度* 返 回 值: 无* 说    明:该函数可以设置任意写入数据长度*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;Addr = WriteAddr % SPI_FLASH_PageSize;count = SPI_FLASH_PageSize - Addr;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;if (Addr == 0) /* 若地址与 SPI_FLASH_PageSize 对齐  */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}else /* 若地址与 SPI_FLASH_PageSize 不对齐 */{if (NumOfPage == 0) /* NumByteToWrite < SPI_FLASH_PageSize */{if (NumOfSingle > count) /* (NumByteToWrite + WriteAddr) > SPI_FLASH_PageSize */{temp = NumOfSingle - count;SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}
}/*** 函数功能: 从串行Flash读取数据* 输入参数: pBuffer:存放读取到数据的指针*           ReadAddr:读取数据目标地址*           NumByteToRead:读取数据长度* 返 回 值: 无* 说    明:该函数可以设置任意读取数据长度*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送 读 指令 */SPI_FLASH_SendByte(W25X_ReadData);/* 发送 读 地址高位 */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* 发送 读 地址低位 */SPI_FLASH_SendByte(ReadAddr & 0xFF);while (NumByteToRead--) /* 读取数据 */{/* 读取一个字节*/*pBuffer = SPI_FLASH_SendByte(Dummy_Byte);/* 指向下一个字节缓冲区 */pBuffer++;}/* 禁用串行FLASH: CS 高电平 */FLASH_SPI_CS_DISABLE();
}/*** 函数功能: 读取串行Flash型号的ID* 输入参数: 无* 返 回 值: u32:串行Flash的型号ID* 说    明:  FLASH_ID      IC型号      存储空间大小         0xEF3015      W25X16        2M byte0xEF4015	    W25Q16        4M byte0XEF4017      W25Q64        8M byte0XEF4018      W25Q128       16M byte  (YS-F1Pro开发板默认配置)*/
u32 SPI_FLASH_ReadID(void)
{u32 Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送命令:读取芯片型号ID */SPI_FLASH_SendByte(W25X_JedecDeviceID);/* 从串行Flash读取一个字节数据 */Temp0 = SPI_FLASH_SendByte(Dummy_Byte);/* 从串行Flash读取一个字节数据 */Temp1 = SPI_FLASH_SendByte(Dummy_Byte);/* 从串行Flash读取一个字节数据 */Temp2 = SPI_FLASH_SendByte(Dummy_Byte);/* 禁用串行Flash:CS高电平 */FLASH_SPI_CS_DISABLE();Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;return Temp;
}/*** 函数功能: 读取串行Flash设备ID* 输入参数: 无* 返 回 值: u32:串行Flash的设备ID* 说    明:*/
u32 SPI_FLASH_ReadDeviceID(void)
{u32 Temp = 0;/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送命令:读取芯片设备ID * */SPI_FLASH_SendByte(W25X_DeviceID);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);SPI_FLASH_SendByte(Dummy_Byte);/* 从串行Flash读取一个字节数据 */Temp = SPI_FLASH_SendByte(Dummy_Byte);/* 禁用串行Flash:CS高电平 */FLASH_SPI_CS_DISABLE();return Temp;
}/*** 函数功能: 启动连续读取数据串* 输入参数: ReadAddr:读取地址* 返 回 值: 无* 说    明:Initiates a read data byte (READ) sequence from the Flash.*           This is done by driving the /CS line low to select the device,*           then the READ instruction is transmitted followed by 3 bytes*           address. This function exit and keep the /CS line low, so the*           Flash still being selected. With this technique the whole*           content of the Flash is read with a single READ instruction.*/
void SPI_FLASH_StartReadSequence(u32 ReadAddr)
{/* Select the FLASH: Chip Select low */FLASH_SPI_CS_ENABLE();/* Send "Read from Memory " instruction */SPI_FLASH_SendByte(W25X_ReadData);/* Send the 24-bit address of the address to read from -----------------------*//* Send ReadAddr high nibble address byte */SPI_FLASH_SendByte((ReadAddr & 0xFF0000) >> 16);/* Send ReadAddr medium nibble address byte */SPI_FLASH_SendByte((ReadAddr& 0xFF00) >> 8);/* Send ReadAddr low nibble address byte */SPI_FLASH_SendByte(ReadAddr & 0xFF);
}/*** 函数功能: 从串行Flash读取一个字节数据* 输入参数: 无* 返 回 值: u8:读取到的数据* 说    明:This function must be used only if the Start_Read_Sequence*           function has been previously called.*/
u8 SPI_FLASH_ReadByte(void)
{return (SPI_FLASH_SendByte(Dummy_Byte));
}/*** 函数功能: 往串行Flash读取写入一个字节数据并接收一个字节数据* 输入参数: byte:待发送数据* 返 回 值: u8:接收到的数据* 说    明:无*/
u8 SPI_FLASH_SendByte(u8 byte)
{/* 循环等待直到SPI 数据寄存器DR为空,即当DR寄存器不为空时持续等待 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET);/* 通过SPI外设发送一个字节数据 */SPI_I2S_SendData(FLASH_SPIx , byte);/* 等待接收到数据 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET);/* 读取SPI总线接收到一个字节数据并返回 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** 函数功能: 往串行Flash读取写入半字(16bit)数据并接收半字数据* 输入参数: byte:待发送数据* 返 回 值: u16:接收到的数据* 说    明:无*/
u16 SPI_FLASH_SendHalfWord(u16 HalfWord)
{/* 循环等待直到SPI 数据寄存器DR为空,即当DR寄存器不为空时持续等待 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_TXE) == RESET);/* 通过SPI外设发送半字数据 */SPI_I2S_SendData(FLASH_SPIx , HalfWord);/* 等待接收到数据 */while (SPI_I2S_GetFlagStatus(FLASH_SPIx , SPI_I2S_FLAG_RXNE) == RESET);/* 读取SPI总线接收到半字数据并返回 */return SPI_I2S_ReceiveData(FLASH_SPIx );
}/*** 函数功能: 使能串行Flash写操作* 输入参数: 无* 返 回 值: 无* 说    明:无*/
void SPI_FLASH_WriteEnable(void)
{/* 选择串行FLASH: CS低电平 */FLASH_SPI_CS_ENABLE();/* 发送命令:写使能 */SPI_FLASH_SendByte(W25X_WriteEnable);/* 禁用串行Flash:CS高电平 */FLASH_SPI_CS_DISABLE();
}/*** 函数功能: 等待数据写入完成* 输入参数: 无* 返 回 值: 无* 说    明:Polls the status of the Write In Progress (WIP) flag in the*           FLASH's status  register  and  loop  until write  opertaion*           has completed.*/
void SPI_FLASH_WaitForWriteEnd(void)
{u8 FLASH_Status = 0;/* Select the FLASH: Chip Select low */FLASH_SPI_CS_ENABLE();/* Send "Read Status Register" instruction */SPI_FLASH_SendByte(W25X_ReadStatusReg);/* Loop as long as the memory is busy with a write cycle */do{/* Send a dummy byte to generate the clock needed by the FLASHand put the value of the status register in FLASH_Status variable */FLASH_Status = SPI_FLASH_SendByte(Dummy_Byte);	 }while ((FLASH_Status & WIP_Flag) == SET); /* Write in progress *//* Deselect the FLASH: Chip Select high */FLASH_SPI_CS_DISABLE();
}/*** 函数功能: 进入掉电模式* 输入参数: 无* 返 回 值: 无* 说    明:无*/
void SPI_Flash_PowerDown(void)   
{ /* Select the FLASH: Chip Select low */FLASH_SPI_CS_ENABLE();/* Send "Power Down" instruction */SPI_FLASH_SendByte(W25X_PowerDown);/* Deselect the FLASH: Chip Select high */FLASH_SPI_CS_DISABLE();
}   /*** 函数功能: 唤醒串行Flash* 输入参数: 无* 返 回 值: 无* 说    明:无*/
void SPI_Flash_WAKEUP(void)   
{/* Select the FLASH: Chip Select low */FLASH_SPI_CS_ENABLE();/* Send "Power Down" instruction */SPI_FLASH_SendByte(W25X_ReleasePowerDown);/* Deselect the FLASH: Chip Select high */FLASH_SPI_CS_DISABLE(); 
}   

2.bsp_spi_flash.h

#ifndef __SPI_FLASH_H__
#define __SPI_FLASH_H__/* 包含头文件 ----------------------------------------------------------------*/
#include <stm32f10x.h>/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
//#define  SPI_FLASH_ID                       0xEF3015     //W25X16
//#define  SPI_FLASH_ID                       0xEF4015	    //W25Q16
//#define  SPI_FLASH_ID                       0XEF4017     //W25Q64
#define  SPI_FLASH_ID                       0XEF4018     //W25Q128  YS-F1Pro开发默认使用/************************** SPI Flash 连接引脚定义********************************/
#define FLASH_SPIx                        SPI1
#define FLASH_SPI_APBxClock_FUN           RCC_APB2PeriphClockCmd
#define FLASH_SPI_CLK                     RCC_APB2Periph_SPI1#define FLASH_SPI_SCK_APBxClock_FUN       RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_CLK                 RCC_APB2Periph_GPIOA   
#define FLASH_SPI_SCK_PORT                GPIOA   
#define FLASH_SPI_SCK_PIN                 GPIO_Pin_5#define FLASH_SPI_MISO_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define FLASH_SPI_MISO_CLK                RCC_APB2Periph_GPIOA    
#define FLASH_SPI_MISO_PORT               GPIOA 
#define FLASH_SPI_MISO_PIN                GPIO_Pin_6#define FLASH_SPI_MOSI_APBxClock_FUN      RCC_APB2PeriphClockCmd
#define FLASH_SPI_MOSI_CLK                RCC_APB2Periph_GPIOA    
#define FLASH_SPI_MOSI_PORT               GPIOA 
#define FLASH_SPI_MOSI_PIN                GPIO_Pin_7#define FLASH_SPI_CS_APBxClock_FUN        RCC_APB2PeriphClockCmd
#define FLASH_SPI_CS_CLK                  RCC_APB2Periph_GPIOA    
#define FLASH_SPI_CS_PORT                 GPIOA
#define FLASH_SPI_CS_PIN                  GPIO_Pin_4#define FLASH_SPI_CS_ENABLE()             GPIO_ResetBits(FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN)
#define FLASH_SPI_CS_DISABLE()            GPIO_SetBits(FLASH_SPI_CS_PORT, FLASH_SPI_CS_PIN)#define CALIBRATE_DATA_ADDR               2*4096/* 扩展变量 ------------------------------------------------------------------*/
/* 函数声明 ------------------------------------------------------------------*/
void SPI_FLASH_Init(void);
void SPI_FLASH_SectorErase(uint32_t SectorAddr);
void SPI_FLASH_BulkErase(void);
void SPI_FLASH_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
void SPI_FLASH_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
uint32_t SPI_FLASH_ReadID(void);
uint32_t SPI_FLASH_ReadDeviceID(void);
void SPI_FLASH_StartReadSequence(uint32_t ReadAddr);
void SPI_Flash_PowerDown(void);
void SPI_Flash_WAKEUP(void);uint8_t SPI_FLASH_ReadByte(void);
uint8_t SPI_FLASH_SendByte(uint8_t byte);
uint16_t SPI_FLASH_SendHalfWord(uint16_t HalfWord);
void SPI_FLASH_WriteEnable(void);
void SPI_FLASH_WaitForWriteEnd(void);#endif /* __SPI_FLASH_H__ */

3.main.c

  /* 包含头文件 ----------------------------------------------------------------*/
#include "stm32f10x.h"
#include "bsp/led/bsp_led.h"
#include "bsp/usart/bsp_debug_usart.h"
#include "bsp/spi_flash/bsp_spi_flash.h"/* 私有类型定义 --------------------------------------------------------------*/
typedef enum { FAILED = 0, PASSED = !FAILED} TestStatus;
/* 私有宏定义 ----------------------------------------------------------------*/
/* 获取缓冲区的长度 */
#define countof(a)      (sizeof(a) / sizeof(*(a)))
#define TxBufferSize1   (countof(TxBuffer1) - 1)
#define RxBufferSize1   (countof(TxBuffer1) - 1)
#define BufferSize      (countof(Tx_Buffer)-1)#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress/* 私有变量 ------------------------------------------------------------------*/
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = " 感谢您选用硬石stm32开发板\n今天是个好日子";
uint8_t Rx_Buffer[BufferSize];__IO uint32_t DeviceID = 0;
__IO uint32_t FlashID = 0;
__IO TestStatus TransferStatus1 = FAILED;/* 扩展变量 ------------------------------------------------------------------*/
/* 私有函数原形 --------------------------------------------------------------*/
static void Delay(uint32_t time);
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength);/* 函数体 --------------------------------------------------------------------*//*** 函数功能: 主函数.* 输入参数: 无* 返 回 值: 无* 说    明: 无*/
int main(void)
{   /* 调试串口初始化配置,115200-N-8-1.使能串口发送和接受 */DEBUG_USART_Init();  /*初始化LED*/LED_GPIO_Init();  /* 调用格式化输出函数打印输出数据 */printf("这是一个16M byte串行flash(W25Q128)读写测试实验\n");  /* 16M串行flash W25Q128初始化 */SPI_FLASH_Init();/* Get SPI Flash Device ID */DeviceID = SPI_FLASH_ReadDeviceID();Delay( 1 );/* Get SPI Flash ID */FlashID = SPI_FLASH_ReadID();printf("FlashID is 0x%X,  Manufacturer Device ID is 0x%X\n", FlashID, DeviceID);/* Check the SPI Flash ID */if (FlashID == SPI_FLASH_ID)  /* #define  sFLASH_ID  0XEF4018 */{	printf("检测到华邦串行flash W25Q128 !\n");/* 擦除SPI的扇区以写入 */SPI_FLASH_SectorErase(FLASH_SectorToErase);	 	 /* 将发送缓冲区的数据写到flash中 */ 	SPI_FLASH_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);SPI_FLASH_BufferWrite(Tx_Buffer, 252, BufferSize);printf("写入的数据为:\n%s \n", Tx_Buffer);/* 将刚刚写入的数据读出来放到接收缓冲区中 */SPI_FLASH_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);printf("读出的数据为:\n %s\n", Rx_Buffer);/* 检查写入的数据与读出的数据是否相等 */TransferStatus1 = Buffercmp(Tx_Buffer, Rx_Buffer, BufferSize);if( PASSED == TransferStatus1 ){    printf("16M串行flash(W25Q128)测试成功!\r");LED1_ON;}else{        printf("16M串行flash(W25Q128)测试失败!\r");LED2_ON;}}else{    printf("获取不到 W25Q128 ID!\n");LED3_ON;}/* 无限循环 */while (1){    }
}/** 函数名:Buffercmp* 描述  :比较两个缓冲区中的数据是否相等* 输入  :-pBuffer1     src缓冲区指针*         -pBuffer2     dst缓冲区指针*         -BufferLength 缓冲区长度* 输出  :无* 返回  :-PASSED pBuffer1 等于   pBuffer2*         -FAILED pBuffer1 不同于 pBuffer2*/
static TestStatus Buffercmp(uint8_t* pBuffer1, uint8_t* pBuffer2, uint16_t BufferLength)
{while(BufferLength--){if(*pBuffer1 != *pBuffer2){return FAILED;}pBuffer1++;pBuffer2++;}return PASSED;
}

四、实验效果

这边展示的是用SPI点亮WS2812b,展示了红色和青色两种效果,如果对WS2812b有兴趣的同学可以自行去了解一下,点亮方式还可以使用PWM波,可以看下这个。
WS2812B彩灯 STM32HAL库开发:PWM+DMA(stm32f103c8t6)_ws2812编程实例-CSDN博客

参考博客:

代码用的是硬石嵌入式开发团队

结束语

本文以STM32VET6为例讲解了如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带,并指出其中的易坑点。希望对大家有所帮助!如果还有什么问题,欢迎评论区留言,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/21485.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

故障预警 vs 故障分类:哪个更有意义,哪个更具挑战性?

故障预警 vs 故障分类&#xff1a;哪个更有意义&#xff0c;哪个更具挑战性&#xff1f; 在现代工业系统中&#xff0c;风力发电机、制造设备等关键装置的可靠性和稳定性对生产效率至关重要。为此&#xff0c;故障预警和故障分类成为保障设备正常运行的重要手段。那么&#xf…

UI案例——登陆系统

UI的登陆界面实例 在学习了UILabel&#xff0c;UIButton&#xff0c;UIView&#xff0c;UITextField的内容之后&#xff0c;我们就可以写一个简单的登陆界面 我们可以通过UILabel来编写我们显示在登陆界面上的文字比方说下面这两行字就是通过UILabel去实现的。 下面给出一下实现…

每日5题Day17 - LeetCode 81 - 85

每一步向前都是向自己的梦想更近一步&#xff0c;坚持不懈&#xff0c;勇往直前&#xff01; 第一题&#xff1a;81. 搜索旋转排序数组 II - 力扣&#xff08;LeetCode&#xff09; class Solution {public boolean search(int[] nums, int target) {int n nums.length;if (n…

【MySQL】MySQL 图形化界面 - 使用说明(MySQL Workbench)

一、安装软件 Navicat&#xff0c;SQLyog 这些软件都不错&#xff0c;不过都需要收费&#xff0c;当然也有破解版。下面用 MySQL Workbench&#xff0c;它是官方提供的工具。 二、使用操作 这个软件本质是一个客户端&#xff0c;现在要让数据库能够远程登录。不过一般不会远程…

Love-Yi情侣网站3.0存在SQL注入漏洞

目录 1. 前言 2. 网站简介 3. 寻找特征点 3.1 第一次尝试 3.2 第二次尝试 4.资产搜索 5.漏洞复现 5.1 寻找漏洞点 5.2 进行进一步测试 5.2.1 手动测试 1.寻找字段 2.寻找回显位 3.查询当前用户 5.2.2 sqlmap去跑 6.总结 1. 前言 朋友说自己建了一个情侣网站,看到…

Golang省市二级联动实现 从数据收集、清洗到数据存储

1.背景&#xff1a; 最近在写项目&#xff0c;在项目中有一个需求是获取用户的地理位置&#xff0c;一开始是打算让前端使用JSON包的形式去实现&#xff0c;但是考虑到后期可能需要对省市的数据做一些修改和控制操作&#xff0c;所以改为后端实现&#xff0c;并向后台暴露一套…

密码加密及验证

目录 为什么需要加密&#xff1f; 密码算法分类 对称密码算法 非对称密码算法 摘要算法 DigestUtils MD5在线解密工具原理 实现用户密码加密 代码实现 为什么需要加密&#xff1f; 在MySQL数据库中&#xff0c;我们常常需要对用户密码、身份证号、手机号码等敏感信息进…

【数据结构】树与二叉树——树的基本概念

树的基本概念 导读一、树的定义二、树的基本术语2.1 结点之间的关系2.2 基本概念2.3 度为m的树与m叉树 三、树的性质结语 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff01;&#xff01;&#xff01; 从今天开始&#xff0c;我们将进入第五章的内容——树与二叉树的学…

微服务:Rabbitmq的WorkQueue模型的使用、默认消费方式(消息队列中间件)

文章目录 WorkQueue模型控制预取消息个数 WorkQueue模型 当然&#xff0c;一个队列&#xff0c;可以由多个消费者去监听。 来实现一下. 生产者&#xff1a; Testpublic void testWorkQueue() throws InterruptedException {// 队列名称String queueName "simple.queue…

NoSQL实战(MongoDB搭建主从复制)

什么是复制集&#xff1f; MongoDB复制是将数据同步到多个服务器的过程&#xff1b; 复制集提供了数据的冗余备份并提高了数据的可用性&#xff0c;通常可以保证数据的安全性&#xff1b; 复制集还允许您从硬件故障和服务中断中恢复数据。 保障数据的安全性 数据高可用性 (2…

Leecode---技巧---只出现一次的数字 / 多数元素

题解&#xff1a; 利用异或运算 a⊕a 0 的性质&#xff0c;可用来消除所有出现了两次的元素&#xff0c;最后剩余的即为所得。 class Solution { public:int singleNumber(vector<int>& nums){// 初始化为0int ans 0;for(int x: nums){// 异或操作ans ^ x;}retur…

关于Openstack删除卷出错的有效解决方案

关于Openstack删除卷时显示卷出错的解决方案 今天删除卷的时候突然发现 删除卷出错 但是还好解决方式还算简单 下面将简洁的写下我的解决方案 当在 Web界面 删除卷时 可能会出现上面的错误 这是因为服务器&#xff0c;出现BUG&#xff0c;卷被附加给了NONE&#xff0c;并且无…

HarmonyOS NEXT星河版之自定义List下拉刷新与加载更多

文章目录 一、加载更多二、下拉刷新三、小结 一、加载更多 借助List的onReachEnd方法&#xff0c;实现加载更多功能&#xff0c;效果如下&#xff1a; Component export struct HPList {// 数据源Prop dataSource: object[] []// 加载更多是否ingState isLoadingMore: bool…

ant X6高亮

先附上效果图 // 节点内属性的点击事件&#xff1a;node:port:click graph.on(‘node:port:click’, ({ node, port }) > { resetAllHighlights(); highlightPort(node, port, true); highlightEdgesForPort(port, new Set()); }); // 以下为源码 <template><div…

Python GNN图神经网络代码实战;GAT代码模版,简单套用,易于修改和提升,图注意力机制代码实战

1.GAT简介 GAT&#xff08;Graph Attention Network&#xff09;模型是一种用于图数据的深度学习模型&#xff0c;由Veličković等人在2018年提出。它通过自适应地在图中计算节点之间的注意力来学习节点之间的关系&#xff0c;并在节点表示中捕捉全局和局部信息。 GAT模型的核…

AI文章互评:得分最高的竟然不是GPT-4!

大家好&#xff0c;我是木易&#xff0c;一个持续关注AI领域的互联网技术产品经理&#xff0c;国内Top2本科&#xff0c;美国Top10 CS研究生&#xff0c;MBA。我坚信AI是普通人变强的“外挂”&#xff0c;所以创建了“AI信息Gap”这个公众号&#xff0c;专注于分享AI全维度知识…

实力!云起无垠晋级“第九届安全创客汇”年度10强

2024年5月28日&#xff0c;第九届“安全创客汇”复赛在重庆圆满落幕。在本次国内最具影响力的网络安全创业大赛中&#xff0c;云起无垠凭借其技术的创新性和巨大市场价值&#xff0c;成功跻身年度十强。 随着人工智能技术的不断发展&#xff0c;特别是在大模型技术的推动下&…

【图像处理与机器视觉】XJTU期末考点

题型 选择&#xff1a;1 分10 填空&#xff1a;1 分15 简答题&#xff08;也含有计算和画图&#xff09;&#xff1a;10 分*4 计算题&#xff1a;15 分20 分 考点 选择题&#xff08;部分&#xff09; 数字图像处理基础 p(x,y),q(s,t)两个像素之间的距离由公式&#xff1a…

湖南(品牌调研)源点咨询 企业品牌调研侧重点分析

本文由湖南长沙&#xff08;市场调研&#xff09;源点咨询编辑发布 企业建立品牌&#xff0c;往往都需进行科学性的品牌调研。因为只有这样&#xff0c;才能让企业更好的把握市场的发展趋势&#xff0c;进而为品牌的建立和发展提供更有价值的数据参考&#xff01;那么品牌的调…

AI精选付费资料包【37GB】

课程介绍 一、人工智能论文合集 二、AI必读经典书籍 三、超详细人工智能学习大纲 四、机器学习基础算法教程 五、深度学习神经网络基础教程 六、计算机视觉实战项目 课程获取 资料&#xff1a;AI精选付费资料包&#xff08;37.4GB&#xff09;获取&#xff1a;扫码关注公z号…