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.rhkb.cn/news/338650.html

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

相关文章

python中利用cartopy库绘制SST图像

1. Cartopy简介 Cartopy 是一个开源的 Python 库&#xff0c;用于绘制地图和地理数据分析。它结合了 matplotlib 的绘图功能和 shapely、pyproj 等库的地理空间数据处理能力&#xff0c;为用户提供了在地图上可视化数据的强大工具。 以下是 Cartopy 的一些主要特点和功能&#…

2、浮动的用法特点,解决父元素高度塌陷解决

一、浮动 用法&#xff1a;浮动就是使用float样式&#xff0c;使元素脱离文档流。属性值有三个&#xff1a;none默认left right 特点&#xff1a; 常用于文字环绕图片浮动的元素脱离文档流影响其他元素排列造成父元素高度塌陷 1、一旦元素设置了浮动&#xff0c;元素就会脱离…

Python知识点14---被规定的资源

提前说一点&#xff1a;如果你是专注于Python开发&#xff0c;那么本系列知识点只是带你入个门再详细的开发点就要去看其他资料了&#xff0c;而如果你和作者一样只是操作其他技术的Python API那就足够了。 在Python中被规定的东西不止有常识中的那些关键字、构造器等编程语言…

汇编原理 | 二进制、跳转指令、算数运算、

一.二进制 two complement reprentation&#xff08;补码&#xff09; 二进制的运算&#xff1a; 6的二进制 0110 -6的二进制 如何表示&#xff1f; 四个bit的第一个bit表示符号&#xff1a;1负0正 -6表示为1010 解释&#xff1a; 0 0000 1 0001 -1 1111&#xff08;由 …

自然语言处理(NLP)—— 置信度(Confidence)

1. 置信度&#xff08;Confidence&#xff09;的概念 置信度&#xff08;Confidence&#xff09;在机器学习和统计中通常指一个模型对其做出的预测是正确的确信程度。在分类任务中&#xff0c;置信度通常由模型赋予特定类别的概率值来表示。例如&#xff0c;在文本分类或实体识…

dm8 什么时候视图中统计的内存会超过OS

v$bufferpool和v$mem_pool视图记录着DMSERVER各组件的内存占用量。理论上跟OS看到的保持一致。但实际大多数场景下&#xff0c;OS中看到的数据远大于视图中的统计。这里面可能有内存泄漏的原因。不过也有的时候视图中的统计数据超过OS。下面就是这种情况&#xff1a; 上图中红线…

Vue插槽与作用域插槽

title: Vue插槽与作用域插槽 date: 2024/6/1 下午9:07:52 updated: 2024/6/1 下午9:07:52 categories: 前端开发 tags:VueSlotScopeSlot组件通信Vue2/3插槽作用域API动态插槽插槽优化 第1章&#xff1a;插槽的概念与原理 插槽的定义 在Vue.js中&#xff0c;插槽&#xff08;…

【OpenHarmony】TypeScript 语法 ④ ( 函数 | TypeScript 具名函数和匿名函数 | 可选参数 | 剩余参数 | 箭头参数 )

文章目录 一、TypeScript 函数1、TypeScript 具名函数和匿名函数2、TypeScript 函数 与 JavaScript 函数对比3、TypeScript 函数 可选参数4、TypeScript 函数 剩余参数5、TypeScript 箭头函数 参考文档 : <HarmonyOS第一课>ArkTS开发语言介绍 一、TypeScript 函数 1、Typ…

【Hive SQL 每日一题】统计指定范围内的有效下单用户

文章目录 测试数据需求说明需求实现 前言&#xff1a;本题制作参考牛客网进阶题目 —— SQL128 未完成试卷数大于1的有效用户 测试数据 -- 创建用户表 DROP TABLE IF EXISTS users; CREATE TABLE users (user_id INT,name STRING,age INT,gender STRING,register_date STRING…

LLM背后的基础模型2:Transformer的组成模块

Transformer是一种先进的语言模型&#xff0c;它在预测下一个单词或标记方面与传统的语言模型有所不同&#xff0c;但仍然遵循相同的基本原理。Transformer通过一系列复杂的步骤&#xff0c;将输入的标记序列转换为能够进行预测的丰富向量序列。 在Transformer中&#xff0c;输…

MySQL8找不到my.ini配置文件以及报sql_mode=only_full_group_by解决方案

一、找不到my.ini配置文件 MySQL 8 安装或启动过程中&#xff0c;如果系统找不到my.ini文件&#xff0c;通常意味着 MySQL服务器没有找到其配置文件。在Windows系统上&#xff0c;MySQL 8 预期使用my.ini作为配置文件&#xff0c;而不是在某些情况下用到的my.cnf文件。 通过 …

极简网络用户手册(1)

极简网络系统处理流程 模块位置&#xff1a;参数平台--专题分析--极简网络分析 步骤&#xff1a; 步骤一&#xff1a;创建精细化场景策略 步骤二&#xff1a;创建任务&#xff0c;主要选择策略&#xff08;包括√配置和距离配置&#xff09;和需要处理的小区清单&#xff08;源…

曲面细分技术在AI去衣中的创新应用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图像处理领域的应用日益广泛。其中&#xff0c;AI去衣技术因其独特的应用场景而备受瞩目。在这一技术的发展过程中&#xff0c;曲面细分技术发挥了至关重要的作用。本文将深入探讨曲面细分技术在AI去衣中的作用及其…

Java Web基础知识(Servlet、Cookie、Session、Filter、Listener)

文章目录 Servlet什么是Servlet&#xff1f;Servlet的生命周期ServletConfig对象ServletContext对象Servlet请求转发和重定向Servlet请求转发&#xff08;forward和include&#xff09;Servlet重定向&#xff08;redirect&#xff09;重定向和转发的区别? 读取文件、下载文件 …

STM32作业实现(一)串口通信

目录 STM32作业设计 STM32作业实现(一)串口通信 STM32作业实现(二)串口控制led STM32作业实现(三)串口控制有源蜂鸣器 STM32作业实现(四)光敏传感器 STM32作业实现(五)温湿度传感器dht11 STM32作业实现(六)闪存保存数据 STM32作业实现(七)OLED显示数据 STM32作业实现(八)触摸按…

Notepad++ 常用

File Edit search view Encoding Language Settings Tools Macro Run Plugins Window 文件 编辑 搜索 视图 编码 语言 设置 工具 宏 运行 插件 窗口 快捷方式 定位行 &#xff1a;CTRL g查找&#xff1a; CTRL F替换&am…

Photoshop 首选项设置建议

Windows Ps菜单&#xff1a;编辑/首选项 Edit/Preferences 快捷键&#xff1a;Ctrl K macOS Ps菜单&#xff1a;Photoshop/首选项 Photoshop/Preferences 快捷键&#xff1a;Cmd K 对 Photoshop 的首选项 Preferences进行设置&#xff0c;可以提高修图与设计效率。下面是一些…

[数据集][目标检测]猕猴桃检测数据集VOC+YOLO格式1838张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1838 标注数量(xml文件个数)&#xff1a;1838 标注数量(txt文件个数)&#xff1a;1838 标注…

SpringBoot整合jasypt加密配置文件敏感信息

SpringBoot整合jasypt加密配置文件敏感信息 在项目中我们需要对配置文件的一些敏感信息进行加密处理&#xff0c;比如数据库账户密码&#xff0c;避免直接暴露出来&#xff0c;这种场景常常用于生产环境&#xff0c;我们不想让开发人员知道生产库的密码&#xff0c;有运维人员…

springboot基础及上传组件封装

简介 本文主要以文件上传为demo&#xff0c;介绍了一些 springboot web 开发的入门的技术栈。 对应刚接触 springboot 的可以参考下。 主要包括文件md5比对、生成图片缩略图、数据库迁移、文件记录持久化、请求全局异常处理等功能。 准备工作 在 idea 中创建项目&#xff…