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。
https://img-blog.csdnimg.cn/direct/b2b6cb0412eb404e992d3010e21e6493.png
3.SPI的时序
数据时钟时序图
通过组合CPOL和CPHA的设置,可以产生四种大概的时序关系,这影响数据的采样和锁存时机。这些设置对SPI主模式和从模式下的装备都实用。
[*]CPOL(时钟极性):控制在没有数据传输时SCK(时钟)引脚的空闲状态电平。
[*]CPOL=0:SCK引脚在空闲状态保持低电平。
[*]CPOL=1:SCK引脚在空闲状态保持高电平。
[*]CPHA(时钟相位):确定命据位的采样和锁存时机。
[*]CPHA=0:数据在第一个时钟边沿被采样和锁存。第一个边沿是降落沿假如CPOL=0,是上升沿假如CPOL=1。
[*]CPHA=1:数据在第二个时钟边沿被采样和锁存。第二个边沿是降落沿假如CPOL=0,是上升沿假如CPOL=1。
https://img-blog.csdnimg.cn/direct/b85edf0e96164f62ac6d36e055ce5908.png
https://img-blog.csdnimg.cn/direct/0378ed13de324c74887722bdb0997a03.png
4.SPI制止
SPI共有5种制止事件,如下图所示:
https://img-blog.csdnimg.cn/direct/fd44a653b1aa48528366b815cf7a44c0.png
二、实验步骤
1.串行FLASH初始化
void SPI_FLASH_Init(void)
{
SPI_InitTypeDefSPI_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_InitTypeDefSPI_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 byte 0xEF4015 W25Q16 4M byte 0XEF4017 W25Q64 8M byte 0XEF4018 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 statusregisterandloopuntil writeopertaion* 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 FLASH and 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>
/* 类型定义 ------------------------------------------------------------------*/
/* 宏定义 --------------------------------------------------------------------*/
//#defineSPI_FLASH_ID 0xEF3015 //W25X16
//#defineSPI_FLASH_ID 0xEF4015 //W25Q16
//#defineSPI_FLASH_ID 0XEF4017 //W25Q64
#defineSPI_FLASH_ID 0XEF4018 //W25Q128YS-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)
#defineFLASH_WriteAddress 0x00000
#defineFLASH_ReadAddress FLASH_WriteAddress
#defineFLASH_SectorToErase FLASH_WriteAddress
/* 私有变量 ------------------------------------------------------------------*/
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = " 感谢您选用硬石stm32开发板\n今天是个好日子";
uint8_t Rx_Buffer;
__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)/* #definesFLASH_ID0XEF4018 */
{
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博客 https://img-blog.csdnimg.cn/direct/43f733a81403408db776314217a6d8db.jpeghttps://img-blog.csdnimg.cn/direct/65555af0440845afb92043f2b569984a.jpeg
参考博客:
代码用的是硬石嵌入式开发团队
竣事语
本文以STM32VET6为例解说了如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带,并指出其中的易坑点。盼望对大家有所帮助!假如还有什么问题,欢迎品评区留言,谢谢!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]