《STM32从零开始学习历程》@EnzoReventon
相关链接:
SPI物理层及FLASH芯片介绍
SPI协议层
SPI特性及架构
参考资料:
[野火EmbedFire]《STM32库开发实战指南——基于野火霸天虎开发板》
[正点原子]STM32F4开发指南-库函数版本_V1.2
[ST]《STM32F4xx中文参考手册》
SPI协议及总线协议介绍
W25Q128产品数据手册
本文使用的外设为SPI1(正点原子F4探索者开发板)、FLASH以及USART1。
USART用来调试程序,我们还是使用USART1,因此将PB9,PB10与TX,RX相连接即可。
查阅正点原子F4探索者开发板硬件手册,了解SPI引脚与GPIO的对应情况。
由上图可以看出:SPI的SCK,MISO,MOSI分别与芯片的PB3,PB4,PB5连接,片选信号F_CS与PB14相连接,因此在后面程序配置的时候需要注意不能配置错引脚。
① 使能SPIx和IO口时钟
RCC_AHBxPeriphClockCmd() / RCC_APBxPeriphClockCmd();
② 初始化IO口为复用功能
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
③ 设置引脚复用映射:
GPIO_PinAFConfig();
② 初始化SPIx,设置SPIx工作模式
void SPI_Init(SPI_TypeDef* SPIx, SPI_InitTypeDef* SPI_InitStruct);
③ 使能SPIx
void SPI_Cmd(SPI_TypeDef* SPIx, FunctionalState NewState);
④ 编写字节发送函数:uint8_t SPI_FLASH_ByteWrite(uint8_t data)
发送数据(指令):void SPI_I2S_SendData(SPI_TypeDef* SPIx, uint16_t Data);
接收返回的数据:uint16_t SPI_I2S_ReceiveData(SPI_TypeDef* SPIx) ;
⑤ 编写擦除扇区函数:void SPI_FLASH_Erase_Sector(uint32_t addr);
⑥ 编写写入数据函数:void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size);
⑦ 编写读取数据函数:void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size);
⑧ 查看SPI传输状态
SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE);
⑨ 编写SPI发送函数
控制片选引脚:GPIO_ResetBits()、GPIO_SetBits();
⑩ 主函数调用,优化程序(超时函数、宏定义、等待空闲函数、写使能函数)
这里大家可以按照自己的需求以及编程习惯进行定义,宏定义的好处在于便于程序的移植,方便改动程序。
/*命令定义-开头*/
#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
/*SPI GPIO 接口*/
#define FLASH_SPI SPI1
#define FLASH_SPI_CLK RCC_APB2Periph_SPI1
#define FLASH_SPI_CLK_INIT RCC_APB2PeriphClockCmd
#define FLASH_SPI_SCK_PIN GPIO_Pin_3
#define FLASH_SPI_SCK_GPIO_PORT GPIOB
#define FLASH_SPI_SCK_GPIO_CLK RCC_AHB1Periph_GPIOB
#define FLASH_SPI_SCK_SOURCE GPIO_PinSource3
#define FLASH_SPI_SCK_AF GPIO_AF_SPI1
#define FLASH_SPI_MOSI_PIN GPIO_Pin_5
#define FLASH_SPI_MOSI_GPIO_PORT GPIOB
#define FLASH_SPI_MOSI_GPIO_CLK RCC_AHB1Periph_GPIOB
#define FLASH_SPI_MOSI_SOURCE GPIO_PinSource5
#define FLASH_SPI_MOSI_AF GPIO_AF_SPI1
#define FLASH_SPI_MISO_PIN GPIO_Pin_4
#define FLASH_SPI_MISO_GPIO_PORT GPIOB
#define FLASH_SPI_MISO_GPIO_CLK RCC_AHB1Periph_GPIOB
#define FLASH_SPI_MISO_SOURCE GPIO_PinSource4
#define FLASH_SPI_MISO_AF GPIO_AF_SPI1
#define FLASH_SPI_CS_PIN GPIO_Pin_14
#define FLASH_SPI_CS_GPIO_PORT GPIOB
#define FLASH_SPI_CS_GPIO_CLK RCC_AHB1Periph_GPIOB
#define FLASH_SPI_CS_LOW() GPIO_ResetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)
#define FLASH_SPI_CS_HIGH() GPIO_SetBits(FLASH_SPI_CS_GPIO_PORT,FLASH_SPI_CS_PIN)
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/*函数申明*/
static void SPI_GPIO_Config(void); //引脚初始化函数
void SPI_Mode_Config(void); //SPI模式初始化函数
void SPI_FLASH_Init(void); //SPI FLASH外设初始化调用函数
uint8_t SPI_FLASH_ByteWrite(uint8_t data); //字节写入函数
uint8_t SPI_FLASH_Read_ID(void); //SPI读取FLASH ID函数
void SPI_FLASH_Erase_Sector(uint32_t addr); //FLASH擦除函数
void SPI_FLASH_Write_Enable(void); //SPI写使能函数
void SPI_FLASH_Wait_For_Standby(void); //SPI等待直到空闲函数
void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size); //SPI连续读函数
void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size); //SPI连续写函数
void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size); //SPI页写入函数
SPI子函数:
//申明超时变量
static uint32_t SPITimeOut = ((uint32_t)(10 * SPIT_FLAG_TIMEOUT));
//申明错误代码返回函数
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode);
/*
================================================
SPI GPIO初始化 复用函数
================================================
*/
static void SPI_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
FLASH_SPI_CLK_INIT(FLASH_SPI_CLK, ENABLE); //使能SPI时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1)
/* 使能 FLASH_SPI 及GPIO 时钟 */
/*!< SPI_FLASH_SPI_CS_GPIO, SPI_FLASH_SPI_MOSI_GPIO,
SPI_FLASH_SPI_MISO_GPIO,SPI_FLASH_SPI_SCK_GPIO 时钟使能 */
RCC_AHB1PeriphClockCmd(FLASH_SPI_SCK_GPIO_CLK | FLASH_SPI_MOSI_GPIO_CLK|
FLASH_SPI_MISO_GPIO_CLK|FLASH_SPI_CS_GPIO_CLK, ENABLE); //初始化GPIO时钟
//查看引脚,初始化SCK,MOSI,MISO,CS引脚GPIO
//设置引脚复用
GPIO_PinAFConfig(FLASH_SPI_SCK_GPIO_PORT, FLASH_SPI_SCK_SOURCE, FLASH_SPI_SCK_AF);
GPIO_PinAFConfig(FLASH_SPI_MOSI_GPIO_PORT, FLASH_SPI_MOSI_SOURCE, FLASH_SPI_MOSI_AF);
GPIO_PinAFConfig(FLASH_SPI_MISO_GPIO_PORT, FLASH_SPI_MISO_SOURCE, FLASH_SPI_MISO_AF);
/*!< 配置 SPI_FLASH_SPI 引脚: SCK */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_SCK_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(FLASH_SPI_SCK_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MISO */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MOSI_PIN;
GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: MOSI */
GPIO_InitStructure.GPIO_Pin = FLASH_SPI_MISO_PIN;
GPIO_Init(FLASH_SPI_MOSI_GPIO_PORT, &GPIO_InitStructure);
/*!< 配置 SPI_FLASH_SPI 引脚: CS */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(FLASH_SPI_CS_GPIO_PORT, &GPIO_InitStructure);
}
/*
================================================
SPI 初始化结构体初始化
================================================
*/
void SPI_Mode_Config(void)
{
/*!< 初始化SPI结构体函数 */
SPI_InitTypeDef SPI_InitStructure;
SPI_InitStructure.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_CPHA=SPI_CPHA_1Edge;
SPI_InitStructure.SPI_CPOL=SPI_CPOL_Low;
SPI_InitStructure.SPI_CRCPolynomial=7;
SPI_InitStructure.SPI_DataSize=SPI_DataSize_8b;
SPI_InitStructure.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
SPI_InitStructure.SPI_FirstBit=SPI_FirstBit_MSB;
SPI_InitStructure.SPI_Mode=SPI_Mode_Master;
SPI_InitStructure.SPI_NSS=SPI_NSS_Soft;
SPI_Init(FLASH_SPI,&SPI_InitStructure);
SPI_Cmd(FLASH_SPI,ENABLE);
}
================================================
SPI FLASH 外设初始化
================================================
*/
void SPI_FLASH_Init(void)
{
SPI_GPIO_Config();
SPI_Mode_Config();
}
/*
================================================
通过SPI发送一个字节
参数:要写入的数据
返回值:错误代码
================================================
*/
uint8_t SPI_FLASH_ByteWrite(uint8_t data)
{
uint8_t re_data;
//等待TXE标志
SPITimeOut = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_TXE) == RESET )
{
if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(1);
}
SPI_I2S_SendData(FLASH_SPI, data);
//等待RXNE标志 来确认发送完成,及准备读取数据
SPITimeOut = SPIT_FLAG_TIMEOUT;
while(SPI_I2S_GetFlagStatus (FLASH_SPI, SPI_I2S_FLAG_RXNE) == RESET )
{
if((SPITimeOut--) == 0) return SPI_TIMEOUT_UserCallback(2);
}
re_data = SPI_I2S_ReceiveData(FLASH_SPI);
return re_data;
}
/*
================================================
读ID0-ID7
================================================
*/
uint8_t SPI_FLASH_Read_ID(void)
{
uint8_t id;
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_ReleasePowerDown);
SPI_FLASH_ByteWrite(0xFF);
SPI_FLASH_ByteWrite(0xFF);
SPI_FLASH_ByteWrite(0xFF);
//接收读取到的内容
id = SPI_FLASH_ByteWrite(0xFF);
FLASH_SPI_CS_HIGH();
return id;
}
/*
================================================
擦除扇区
addr:必须对齐到要擦除的扇区的首地址
================================================
*/
void SPI_FLASH_Erase_Sector(uint32_t addr)
{
//写使能
SPI_FLASH_Write_Enable();
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_SectorErase);
//发送要擦除的地址
SPI_FLASH_ByteWrite((addr>>16) & 0xFF);
SPI_FLASH_ByteWrite((addr>>8) & 0xFF);
SPI_FLASH_ByteWrite(addr & 0xFF);
FLASH_SPI_CS_HIGH();
//等待内部时序完成
SPI_FLASH_Wait_For_Standby();
}
/*
================================================
写入数据
addr:要写入数据的首地址,
buf:存储写入的数据的指针
size:要写入多少个数据 不超过256
================================================
*/
void SPI_FLASH_Page_Write(uint32_t addr, uint8_t *buf, uint32_t size)
{
//写使能
SPI_FLASH_Write_Enable();
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_PageProgram);
//发送要写入的地址
SPI_FLASH_ByteWrite((addr>>16) & 0xFF);
SPI_FLASH_ByteWrite((addr>>8) & 0xFF);
SPI_FLASH_ByteWrite(addr & 0xFF);
while(size--)
{
SPI_FLASH_ByteWrite(*buf);
buf++;
}
FLASH_SPI_CS_HIGH();
//等待内部时序完成
SPI_FLASH_Wait_For_Standby();
}
/*
================================================
读取数据
addr:要读取数据的首地址,
buf:存储读取到的数据的指针
size:要读取多少个数据
================================================
*/
void SPI_FLASH_Read_Buff(uint32_t addr, uint8_t *buf, uint32_t size)
{
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_ReadData);
//发送要读取的地址
SPI_FLASH_ByteWrite((addr>>16) & 0xFF);
SPI_FLASH_ByteWrite((addr>>8) & 0xFF);
SPI_FLASH_ByteWrite(addr & 0xFF);
while(size--)
{
*buf = SPI_FLASH_ByteWrite(0xFF);
buf++;
}
FLASH_SPI_CS_HIGH();
}
/*
================================================
写入数据
addr:要写入数据的首地址,
buf:要写入的数据的指针
size:要写入多少个数据 不超过256
================================================
*/
void SPI_FLASH_Write_Buff(uint32_t addr, uint8_t *buf, uint32_t size)
{
uint32_t count=0;//计算循环次数
while(size--)
{
count++;
//第一次执行,第257次,256*2+1,256*3+1,addr对齐到4096时
if(count == 1 || (count%256) ==1 || (addr%4096)==0)
{
//结束上一次的页写入指令
FLASH_SPI_CS_HIGH();
//等待上一次页写入的完成
SPI_FLASH_Wait_For_Standby();
//写使能,每次写入前都必须调用
SPI_FLASH_Write_Enable();
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_PageProgram);
//发送要写入的地址
SPI_FLASH_ByteWrite((addr>>16) & 0xFF);
SPI_FLASH_ByteWrite((addr>>8) & 0xFF);
SPI_FLASH_ByteWrite(addr & 0xFF);
}
SPI_FLASH_ByteWrite(*buf);
buf++;
addr++;
}
FLASH_SPI_CS_HIGH();
//等待内部时序完成
SPI_FLASH_Wait_For_Standby();
}
/*
================================================
写使能
================================================
*/
void SPI_FLASH_Write_Enable(void)
{
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码
SPI_FLASH_ByteWrite(W25X_WriteEnable);
FLASH_SPI_CS_HIGH();
}
/*
================================================
等待直到空闲状态
================================================
*/
void SPI_FLASH_Wait_For_Standby(void)
{
uint8_t status ;
//控制片选引脚
FLASH_SPI_CS_LOW();
//指令代码 0x05 检测读取标志位
SPI_FLASH_ByteWrite(W25X_ReadStatusReg);
SPITimeOut = SPIT_LONG_TIMEOUT;
while(1)
{
status = SPI_FLASH_ByteWrite(0xFF);
//如果条件成立,说明为空闲状态
if((status & 0x01) == 0)
break;
//若SPITimeout为0,表示已检测SPITimeout次都仍为忙碌,跳出循环
if((SPITimeOut--)==0)
{
SPI_TIMEOUT_UserCallback(3);
break;
}
}
FLASH_SPI_CS_HIGH();
}
/*
================================================
错误代码返回函数
参数:写如的错误代码
返回值:错误代码
================================================
*/
static uint32_t SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* Block communication and all processes */
printf("\r\nSPI 等待超时!errorCode = %d\r\n",errorCode);
return errorCode;
}
主函数
uint8_t read_buff[4096] = {
0};
uint8_t write_buff[4096] = {
0};
int main(void)
{
int i = 0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置系统中断优先级分组2
delay_init(168); //延时初始化
uart_init(115200); //串口初始化波特率为115200
LED_Init(); //初始化与LED连接的硬件接口
SPI_FLASH_Init();
printf("\r\n=======================================\r\n");
printf("this is id : 0x%x",SPI_FLASH_Read_ID());
printf("\r\n擦除开始");
//擦除测试
SPI_FLASH_Erase_Sector(4096*0);
//擦除测试
SPI_FLASH_Erase_Sector(4096*1);
printf("\r\n擦除完成");
SPI_FLASH_Read_Buff(0,read_buff,4096);
for(i=0;i<4096;i++)
{
//若不等于0xFF,说明擦除不成功
if(read_buff[i] != 0xFF)
{
printf("\r\n擦除失败");
}
}
printf("\r\n擦除完成");
//初始化要写入的数据
for(i=0;i<256;i++)
{
write_buff[i] = i;
}
printf("\r\n开始写入");
SPI_FLASH_Write_Buff(0,write_buff,256); //写入数据 0为扇区首地址 write_buff为数据地址,256为写入数据数量
printf("\r\n写入完成");
SPI_FLASH_Read_Buff(0,write_buff,4096);
printf("\r\n读取到的数据:\r\n");
for(i=0;i<4096;i++) //循环打印输出
{
printf("0x%02x ",write_buff[i]);
}
while (1)
{
}
}
可以看到前256个数据已经被写入为我们想要写入的数据了,其余数据均为擦除后的0xFF。
//初始化要写入的数据
for(i=0;i<4096;i++)
{
write_buff[i] = i;
}
printf("\r\n开始写入");
SPI_FLASH_Write_Buff(0,write_buff,4096);
printf("\r\n写入完成");
SPI_FLASH_Read_Buff(0,write_buff,4096);
printf("\r\n读取到的数据:\r\n");
for(i=0;i<4096;i++)
{
printf("0x%02x ",write_buff[i]);
}
可以看出整个扇区的数据都被写入。